diff --git a/helix-web/.env b/helix-web/.env new file mode 100644 index 0000000..e69de29 diff --git a/helix-web/.env.development b/helix-web/.env.development new file mode 100644 index 0000000..e69de29 diff --git a/helix-web/.env.example b/helix-web/.env.example new file mode 100644 index 0000000..0a09ae2 --- /dev/null +++ b/helix-web/.env.example @@ -0,0 +1,14 @@ +# Backend base URL +VITE_API_BASE_URL=/api +# Microsoft Entra ID Client ID +VITE_MSAL_CLIENT_ID=msal-client-id +# Microsoft Entra ID Tenant ID +VITE_MSAL_TENANT_ID=msal-tenant-id +# Default region of user. For more regions, please refer to ISO 3166-1 alpha-2 code +# Examples +# - GB: United Kingdom +# - US: United States +# - DE: Germany +VITE_DEFAULT_REGION_ABBREVIATION=GB +# Application title +VITE_APP_TITLE='OnixByte Technology Co., Ltd' diff --git a/helix-web/.env.production b/helix-web/.env.production new file mode 100644 index 0000000..e69de29 diff --git a/helix-web/.gitignore b/helix-web/.gitignore new file mode 100644 index 0000000..22f6f8b --- /dev/null +++ b/helix-web/.gitignore @@ -0,0 +1,105 @@ +### macOS +# General +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Linux +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# Metadata left by Dolphin file manager, which comes with KDE Plasma +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# Log files created by default by the nohup command +nohup.out + +### Windows + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### JetBrains IDE +# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea + +# Gradle and Maven with auto-import +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based HTTP Client +http-client.private.env.json + +# Node libraries +node_modules/ + +# Config +.env.local +.env.*.local diff --git a/helix-web/.prettierrc.yml b/helix-web/.prettierrc.yml new file mode 100644 index 0000000..7612d53 --- /dev/null +++ b/helix-web/.prettierrc.yml @@ -0,0 +1,24 @@ +printWidth: 100 +tabWidth: 2 +useTabs: false +semi: false +singleQuote: false +quoteProps: as-needed +jsxSingleQuote: false +trailingComma: es5 +bracketSpacing: true +bracketSameLine: true +arrowParens: always +#rangeStart: 0 +#rangeEnd: n +#parser: +#filepath: +#requirePragma: false +#insertPragma: false +#proseWrap: preserve +htmlWhitespaceSensitivity: css +#vueIndentScriptAndStyle: false +endOfLine: lf +#embeddedLanguageFormatting: auto +# Enforce single attribute per line in HTML, Vue and JSX +#singleAttributePerLine: false \ No newline at end of file diff --git a/helix-web/eslint.config.js b/helix-web/eslint.config.js new file mode 100644 index 0000000..6f4a2f1 --- /dev/null +++ b/helix-web/eslint.config.js @@ -0,0 +1,42 @@ +import js from "@eslint/js" +import globals from "globals" +import reactHooks from "eslint-plugin-react-hooks" +import reactRefresh from "eslint-plugin-react-refresh" +import tseslint from "typescript-eslint" +import { globalIgnores } from "eslint/config" + +export default tseslint.config([ + globalIgnores(["dist"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + js.configs.recommended, + reactHooks.configs["recommended-latest"], + reactRefresh.configs.vite, + // Remove tseslint.configs.recommended and use this for stricter rules + ...tseslint.configs.strictTypeChecked, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + project: ["./tsconfig.node.json", "./tsconfig.app.json"], + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + "@typescript-eslint/no-unused-vars": 0, + "@typescript-eslint/no-non-null-assertion": 0, + "@typescript-eslint/restrict-template-expressions": 0, + "@typescript-eslint/no-unsafe-assignment": 0, + "@typescript-eslint/no-unsafe-call": 0, + "no-empty": 0, + "@typescript-eslint/no-unsafe-member-access": 0, + "no-empty-pattern": 0, + "@typescript-eslint/no-unnecessary-condition": 0, + "@typescript-eslint/no-confusing-void-expression": 0, + "@typescript-eslint/no-empty-object-type": 0, + "react-refresh/only-export-components": 0, + }, + }, +]) diff --git a/helix-web/index.html b/helix-web/index.html new file mode 100644 index 0000000..48e94cc --- /dev/null +++ b/helix-web/index.html @@ -0,0 +1,13 @@ + + + + + + + Helix + + +
+ + + diff --git a/helix-web/package.json b/helix-web/package.json new file mode 100644 index 0000000..ff59aaa --- /dev/null +++ b/helix-web/package.json @@ -0,0 +1,42 @@ +{ + "name": "helix-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/cssinjs": "^2.0.3", + "@ant-design/icons": "^6.1.0", + "@azure/msal-browser": "^4.28.1", + "@azure/msal-react": "^3.0.25", + "@reduxjs/toolkit": "^2.11.2", + "@tailwindcss/vite": "^4.1.18", + "antd": "^6.2.2", + "axios": "^1.13.4", + "dayjs": "^1.11.19", + "libphonenumber-js": "^1.12.35", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-redux": "^9.2.0", + "react-router": "^7.13.0", + "react-router-dom": "^7.13.0", + "redux-persist": "^6.0.0", + "tailwindcss": "^4.1.18" + }, + "devDependencies": { + "@types/node": "^20.19.30", + "@types/react": "^19.2.10", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^4.7.0", + "globals": "^16.5.0", + "prettier": "^3.8.1", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "vite-plugin-svgr": "^4.5.0" + } +} diff --git a/helix-web/pnpm-lock.yaml b/helix-web/pnpm-lock.yaml new file mode 100644 index 0000000..ba3e337 --- /dev/null +++ b/helix-web/pnpm-lock.yaml @@ -0,0 +1,3072 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/cssinjs': + specifier: ^2.0.3 + version: 2.0.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@ant-design/icons': + specifier: ^6.1.0 + version: 6.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@azure/msal-browser': + specifier: ^4.28.1 + version: 4.28.1 + '@azure/msal-react': + specifier: ^3.0.25 + version: 3.0.25(@azure/msal-browser@4.28.1)(react@19.2.4) + '@reduxjs/toolkit': + specifier: ^2.11.2 + version: 2.11.2(react-redux@9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1))(react@19.2.4) + '@tailwindcss/vite': + specifier: ^4.1.18 + version: 4.1.18(vite@7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2)) + antd: + specifier: ^6.2.2 + version: 6.2.2(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + axios: + specifier: ^1.13.4 + version: 1.13.4 + dayjs: + specifier: ^1.11.19 + version: 1.11.19 + libphonenumber-js: + specifier: ^1.12.35 + version: 1.12.35 + react: + specifier: ^19.2.4 + version: 19.2.4 + react-dom: + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) + react-redux: + specifier: ^9.2.0 + version: 9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1) + react-router: + specifier: ^7.13.0 + version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router-dom: + specifier: ^7.13.0 + version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + redux-persist: + specifier: ^6.0.0 + version: 6.0.0(react@19.2.4)(redux@5.0.1) + tailwindcss: + specifier: ^4.1.18 + version: 4.1.18 + devDependencies: + '@types/node': + specifier: ^20.19.30 + version: 20.19.30 + '@types/react': + specifier: ^19.2.10 + version: 19.2.10 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.10) + '@vitejs/plugin-react': + specifier: ^4.7.0 + version: 4.7.0(vite@7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2)) + globals: + specifier: ^16.5.0 + version: 16.5.0 + prettier: + specifier: ^3.8.1 + version: 3.8.1 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2) + vite-plugin-svgr: + specifier: ^4.5.0 + version: 4.5.0(rollup@4.57.0)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2)) + +packages: + + '@ant-design/colors@8.0.1': + resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==} + + '@ant-design/cssinjs-utils@2.0.2': + resolution: {integrity: sha512-Mq3Hm6fJuQeFNKSp3+yT4bjuhVbdrsyXE2RyfpJFL0xiYNZdaJ6oFaE3zFrzmHbmvTd2Wp3HCbRtkD4fU+v2ZA==} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + '@ant-design/cssinjs@2.0.3': + resolution: {integrity: sha512-HAo8SZ3a6G8v6jT0suCz1270na6EA3obeJWM4uzRijBhdwdoMAXWK2f4WWkwB28yUufsfk3CAhN1coGPQq4kNQ==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@3.0.1': + resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} + + '@ant-design/icons@6.1.0': + resolution: {integrity: sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/react-slick@2.0.0': + resolution: {integrity: sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==} + peerDependencies: + react: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@azure/msal-browser@4.28.1': + resolution: {integrity: sha512-al2u2fTchbClq3L4C1NlqLm+vwKfhYCPtZN2LR/9xJVaQ4Mnrwf5vANvuyPSJHcGvw50UBmhuVmYUAhTEetTpA==} + engines: {node: '>=0.8.0'} + + '@azure/msal-common@15.14.1': + resolution: {integrity: sha512-IkzF7Pywt6QKTS0kwdCv/XV8x8JXknZDvSjj/IccooxnP373T5jaadO3FnOrbWo3S0UqkfIDyZNTaQ/oAgRdXw==} + engines: {node: '>=0.8.0'} + + '@azure/msal-react@3.0.25': + resolution: {integrity: sha512-BtcfBJQrtkfir4mDJ6X/55BT8WL59/QwfEgxGExY/gZLRfjGrqw/VwXiyQRFyLLaVbvKngF0a8rOcFZx1Jr9qQ==} + engines: {node: '>=10'} + peerDependencies: + '@azure/msal-browser': ^4.28.1 + react: ^16.8.0 || ^17 || ^18 || ^19.2.1 + + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rc-component/async-validator@5.1.0': + resolution: {integrity: sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==} + engines: {node: '>=14.x'} + + '@rc-component/cascader@1.11.0': + resolution: {integrity: sha512-VDiEsskThWi8l0/1Nquc9I4ytcMKQYAb9Jkm6wiX5O5fpcMRsm+b8OulBMbr/b4rFTl/2y2y4GdKqQ+2whD+XQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/checkbox@1.0.1': + resolution: {integrity: sha512-08yTH8m+bSm8TOqbybbJ9KiAuIATti6bDs2mVeSfu4QfEnyeF6X0enHVvD1NEAyuBWEAo56QtLe++MYs2D9XiQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/collapse@1.2.0': + resolution: {integrity: sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/color-picker@3.0.3': + resolution: {integrity: sha512-V7gFF9O7o5XwIWafdbOtqI4BUUkEUkgdBwp6favy3xajMX/2dDqytFaiXlcwrpq6aRyPLp5dKLAG5RFKLXMeGA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@2.0.1': + resolution: {integrity: sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/dialog@1.8.2': + resolution: {integrity: sha512-CwDSjpjZ1FcgsdKFPuSoYfi9Vbt2bp+ak4Pzkwq4APQC8DopJKWetRu1V+HE9vI1CNAeqvT5WAvAxE6RiDhl7A==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/drawer@1.4.1': + resolution: {integrity: sha512-kNJQie/QjJO5wGeWrZQwSGeuo8staxXx1nYN+dpK2UY7i8teo5PQdZ6ukKSnnW9vmPXsLn3F5nKYRbf43e8+5g==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/dropdown@1.0.2': + resolution: {integrity: sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + '@rc-component/form@1.6.2': + resolution: {integrity: sha512-OgIn2RAoaSBqaIgzJf/X6iflIa9LpTozci1lagLBdURDFhGA370v0+T0tXxOi8YShMjTha531sFhwtnrv+EJaQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/image@1.6.0': + resolution: {integrity: sha512-tSfn2ZE/oP082g4QIOxeehkmgnXB7R+5AFj/lIFr4k7pEuxHBdyGIq9axoCY9qea8NN0DY6p4IB/F07tLqaT5A==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/input-number@1.6.2': + resolution: {integrity: sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/input@1.1.2': + resolution: {integrity: sha512-Q61IMR47piUBudgixJ30CciKIy9b1H95qe7GgEKOmSJVJXvFRWJllJfQry9tif+MX2cWFXWJf/RXz4kaCeq/Fg==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@rc-component/mentions@1.6.0': + resolution: {integrity: sha512-KIkQNP6habNuTsLhUv0UGEOwG67tlmE7KNIJoQZZNggEZl5lQJTytFDb69sl5CK3TDdISCTjKP3nGEBKgT61CQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/menu@1.2.0': + resolution: {integrity: sha512-VWwDuhvYHSnTGj4n6bV3ISrLACcPAzdPOq3d0BzkeiM5cve8BEYfvkEhNoM0PLzv51jpcejeyrLXeMVIJ+QJlg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.0': + resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==} + engines: {node: '>=8.x'} + + '@rc-component/motion@1.1.6': + resolution: {integrity: sha512-aEQobs/YA0kqRvHIPjQvOytdtdRVyhf/uXAal4chBjxDu6odHckExJzjn2D+Ju1aKK6hx3pAs6BXdV9+86xkgQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mutate-observer@2.0.1': + resolution: {integrity: sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/notification@1.2.0': + resolution: {integrity: sha512-OX3J+zVU7rvoJCikjrfW7qOUp7zlDeFBK2eA3SFbGSkDqo63Sl4Ss8A04kFP+fxHSxMDIS9jYVEZtU1FNCFuBA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/overflow@1.0.0': + resolution: {integrity: sha512-GSlBeoE0XTBi5cf3zl8Qh7Uqhn7v8RrlJ8ajeVpEkNe94HWy5l5BQ0Mwn2TVUq9gdgbfEMUmTX7tJFAg7mz0Rw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/pagination@1.2.0': + resolution: {integrity: sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/picker@1.9.0': + resolution: {integrity: sha512-OLisdk8AWVCG9goBU1dWzuH5QlBQk8jktmQ6p0/IyBFwdKGwyIZOSjnBYo8hooHiTdl0lU+wGf/OfMtVBw02KQ==} + engines: {node: '>=12.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + '@rc-component/portal@2.2.0': + resolution: {integrity: sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ==} + engines: {node: '>=12.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/progress@1.0.2': + resolution: {integrity: sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.1.1': + resolution: {integrity: sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/rate@1.0.1': + resolution: {integrity: sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/resize-observer@1.1.1': + resolution: {integrity: sha512-NfXXMmiR+SmUuKE1NwJESzEUYUFWIDUn2uXpxCTOLwiRUUakd62DRNFjRJArgzyFW8S5rsL4aX5XlyIXyC/vRA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/segmented@1.3.0': + resolution: {integrity: sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@rc-component/select@1.5.2': + resolution: {integrity: sha512-7wqD5D4I2+fc5XoB4nzDDK656QPlDnFAUaxLljkU1wwSpi4+MZxndv9vgg7NQfveuuf0/ilUdOjuPg7NPl7Mmg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/slider@1.0.1': + resolution: {integrity: sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/steps@1.2.2': + resolution: {integrity: sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/switch@1.0.3': + resolution: {integrity: sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/table@1.9.1': + resolution: {integrity: sha512-FVI5ZS/GdB3BcgexfCYKi3iHhZS3Fr59EtsxORszYGrfpH1eWr33eDNSYkVfLI6tfJ7vftJDd9D5apfFWqkdJg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/tabs@1.7.0': + resolution: {integrity: sha512-J48cs2iBi7Ho3nptBxxIqizEliUC+ExE23faspUQKGQ550vaBlv3aGF8Epv/UB1vFWeoJDTW/dNzgIU0Qj5i/w==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/textarea@1.1.2': + resolution: {integrity: sha512-9rMUEODWZDMovfScIEHXWlVZuPljZ2pd1LKNjslJVitn4SldEzq5vO1CL3yy3Dnib6zZal2r2DPtjy84VVpF6A==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tooltip@1.4.0': + resolution: {integrity: sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/tour@2.3.0': + resolution: {integrity: sha512-K04K9r32kUC+auBSQfr+Fss4SpSIS9JGe56oq/ALAX0p+i2ylYOI1MgR83yBY7v96eO6ZFXcM/igCQmubps0Ow==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tree-select@1.6.0': + resolution: {integrity: sha512-UvEGmZT+gcVvRwImAZg3/sXw9nUdn4FmCs1rSIMWjEXEIAo0dTGmIyWuLCvs+1rGe9AZ7CHMPiQUEbdadwV0fw==} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/tree@1.1.0': + resolution: {integrity: sha512-HZs3aOlvFgQdgrmURRc/f4IujiNBf4DdEeXUlkS0lPoLlx9RoqsZcF0caXIAMVb+NaWqKtGQDnrH8hqLCN5zlA==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/trigger@3.9.0': + resolution: {integrity: sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/upload@1.1.0': + resolution: {integrity: sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/util@1.7.0': + resolution: {integrity: sha512-tIvIGj4Vl6fsZFvWSkYw9sAfiCKUXMyhVz6kpKyZbwyZyRPqv2vxYZROdaO1VB4gqTNvUZFXh6i3APUiterw5g==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/virtual-list@1.0.2': + resolution: {integrity: sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@reduxjs/toolkit@2.11.2': + resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.57.0': + resolution: {integrity: sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.0': + resolution: {integrity: sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.0': + resolution: {integrity: sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.0': + resolution: {integrity: sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.0': + resolution: {integrity: sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.0': + resolution: {integrity: sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.0': + resolution: {integrity: sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.57.0': + resolution: {integrity: sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.57.0': + resolution: {integrity: sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.57.0': + resolution: {integrity: sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.57.0': + resolution: {integrity: sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.57.0': + resolution: {integrity: sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.57.0': + resolution: {integrity: sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.57.0': + resolution: {integrity: sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.57.0': + resolution: {integrity: sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.57.0': + resolution: {integrity: sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.57.0': + resolution: {integrity: sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.57.0': + resolution: {integrity: sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.57.0': + resolution: {integrity: sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.57.0': + resolution: {integrity: sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.0': + resolution: {integrity: sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.0': + resolution: {integrity: sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.0': + resolution: {integrity: sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.0': + resolution: {integrity: sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.0': + resolution: {integrity: sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.18': + resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@20.19.30': + resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.10': + resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + antd@6.2.2: + resolution: {integrity: sha512-f5RvWnhjt2gZTpBMW3msHwA3IeaCJBHDwVyEsskYGp0EXcRhhklWrltkybDki0ysBNywkjLPp3wuuWhIKfplcQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.13.4: + resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==} + + baseline-browser-mapping@2.9.18: + resolution: {integrity: sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==} + hasBin: true + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001766: + resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.279: + resolution: {integrity: sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==} + + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + immer@11.1.3: + resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-mobile@5.0.0: + resolution: {integrity: sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + libphonenumber-js@1.12.35: + resolution: {integrity: sha512-T/Cz6iLcsZdb5jDncDcUNhSAJ0VlSC9TnsqtBNdpkaAmy24/R1RhErtNWVWBrcUZKs9hSgaVsBkc7HxYnazIfw==} + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-router-dom@7.13.0: + resolution: {integrity: sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.13.0: + resolution: {integrity: sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + redux-persist@6.0.0: + resolution: {integrity: sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==} + peerDependencies: + react: '>=16' + redux: '>4.0.0' + peerDependenciesMeta: + react: + optional: true + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.57.0: + resolution: {integrity: sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vite-plugin-svgr@4.5.0: + resolution: {integrity: sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==} + peerDependencies: + vite: '>=2.6.0' + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + +snapshots: + + '@ant-design/colors@8.0.1': + dependencies: + '@ant-design/fast-color': 3.0.1 + + '@ant-design/cssinjs-utils@2.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@ant-design/cssinjs': 2.0.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@babel/runtime': 7.28.6 + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@ant-design/cssinjs@2.0.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + csstype: 3.2.3 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + stylis: 4.3.6 + + '@ant-design/fast-color@3.0.1': {} + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@6.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@ant-design/colors': 8.0.1 + '@ant-design/icons-svg': 4.4.2 + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@ant-design/react-slick@2.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + clsx: 2.1.1 + json2mq: 0.2.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + throttle-debounce: 5.0.2 + + '@azure/msal-browser@4.28.1': + dependencies: + '@azure/msal-common': 15.14.1 + + '@azure/msal-common@15.14.1': {} + + '@azure/msal-react@3.0.25(@azure/msal-browser@4.28.1)(react@19.2.4)': + dependencies: + '@azure/msal-browser': 4.28.1 + react: 19.2.4 + + '@babel/code-frame@7.28.6': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.6': {} + + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.6': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/traverse@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@emotion/hash@0.8.0': {} + + '@emotion/unitless@0.7.5': {} + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rc-component/async-validator@5.1.0': + dependencies: + '@babel/runtime': 7.28.6 + + '@rc-component/cascader@1.11.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/select': 1.5.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/tree': 1.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/checkbox@1.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/collapse@1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/color-picker@3.0.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@ant-design/fast-color': 3.0.1 + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/context@2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/dialog@1.8.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/portal': 2.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/drawer@1.4.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/portal': 2.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/dropdown@1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/trigger': 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/form@1.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/async-validator': 5.1.0 + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/image@1.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/portal': 2.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/input-number@1.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/mini-decimal': 1.1.0 + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/input@1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/mentions@1.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/input': 1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/menu': 1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/textarea': 1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/trigger': 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/menu@1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/overflow': 1.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/trigger': 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/mini-decimal@1.1.0': + dependencies: + '@babel/runtime': 7.28.6 + + '@rc-component/motion@1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/mutate-observer@2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/notification@1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/overflow@1.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/pagination@1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/picker@1.9.0(dayjs@1.11.19)(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/overflow': 1.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/trigger': 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + dayjs: 1.11.19 + moment: 2.30.1 + + '@rc-component/portal@2.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/progress@1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/qrcode@1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/rate@1.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/resize-observer@1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/segmented@1.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/select@1.5.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/overflow': 1.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/trigger': 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/virtual-list': 1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/slider@1.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/steps@1.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/switch@1.0.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/table@1.9.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/context': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/virtual-list': 1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/tabs@1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/dropdown': 1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/menu': 1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/textarea@1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/input': 1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/tooltip@1.4.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/trigger': 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/tour@2.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/portal': 2.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/trigger': 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/tree-select@1.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/select': 1.5.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/tree': 1.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/tree@1.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/virtual-list': 1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/trigger@3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/portal': 2.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/upload@1.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@rc-component/util@1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + is-mobile: 5.0.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-is: 18.3.1 + + '@rc-component/virtual-list@1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1))(react@19.2.4)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.3 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.2.4 + react-redux: 9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1) + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/pluginutils@5.3.0(rollup@4.57.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.57.0 + + '@rollup/rollup-android-arm-eabi@4.57.0': + optional: true + + '@rollup/rollup-android-arm64@4.57.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.0': + optional: true + + '@rollup/rollup-darwin-x64@4.57.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.0': + optional: true + + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@svgr/babel-preset@8.1.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.6) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.6) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.6) + + '@svgr/core@8.1.0(typescript@5.9.3)': + dependencies: + '@babel/core': 7.28.6 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.6) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.9.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.28.6 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': + dependencies: + '@babel/core': 7.28.6 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.6) + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + tailwindcss: 4.1.18 + vite: 7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2) + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/estree@1.0.8': {} + + '@types/node@20.19.30': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.10)': + dependencies: + '@types/react': 19.2.10 + + '@types/react@19.2.10': + dependencies: + csstype: 3.2.3 + + '@types/use-sync-external-store@0.0.6': {} + + '@vitejs/plugin-react@4.7.0(vite@7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2) + transitivePeerDependencies: + - supports-color + + antd@6.2.2(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@ant-design/colors': 8.0.1 + '@ant-design/cssinjs': 2.0.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@ant-design/cssinjs-utils': 2.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@ant-design/fast-color': 3.0.1 + '@ant-design/icons': 6.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@ant-design/react-slick': 2.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@babel/runtime': 7.28.6 + '@rc-component/cascader': 1.11.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/checkbox': 1.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/collapse': 1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/color-picker': 3.0.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/dialog': 1.8.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/drawer': 1.4.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/dropdown': 1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/form': 1.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/image': 1.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/input': 1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/input-number': 1.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/mentions': 1.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/menu': 1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/motion': 1.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/mutate-observer': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/notification': 1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/pagination': 1.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/picker': 1.9.0(dayjs@1.11.19)(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/progress': 1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/qrcode': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/rate': 1.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/segmented': 1.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/select': 1.5.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/slider': 1.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/steps': 1.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/switch': 1.0.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/table': 1.9.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/tabs': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/textarea': 1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/tooltip': 1.4.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/tour': 2.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/tree': 1.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/tree-select': 1.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/trigger': 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/upload': 1.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rc-component/util': 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + clsx: 2.1.1 + dayjs: 1.11.19 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + argparse@2.0.1: {} + + asynckit@0.4.0: {} + + axios@1.13.4: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + baseline-browser-mapping@2.9.18: {} + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.18 + caniuse-lite: 1.0.30001766 + electron-to-chromium: 1.5.279 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001766: {} + + clsx@2.1.1: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + compute-scroll-into-view@3.1.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + + csstype@3.2.3: {} + + dayjs@1.11.19: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + detect-libc@2.1.2: {} + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.279: {} + + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + estree-walker@2.0.2: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + globals@16.5.0: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + immer@11.1.3: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + is-arrayish@0.2.1: {} + + is-mobile@5.0.0: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + json5@2.2.3: {} + + libphonenumber-js@1.12.35: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + lines-and-columns@1.2.4: {} + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + moment@2.30.1: + optional: true + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-releases@2.0.27: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.28.6 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.8.1: {} + + proxy-from-env@1.1.0: {} + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-is@18.3.1: {} + + react-redux@9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + redux: 5.0.1 + + react-refresh@0.17.0: {} + + react-router-dom@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + cookie: 1.1.1 + react: 19.2.4 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.4(react@19.2.4) + + react@19.2.4: {} + + redux-persist@6.0.0(react@19.2.4)(redux@5.0.1): + dependencies: + redux: 5.0.1 + optionalDependencies: + react: 19.2.4 + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + reselect@5.1.1: {} + + resolve-from@4.0.0: {} + + rollup@4.57.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.0 + '@rollup/rollup-android-arm64': 4.57.0 + '@rollup/rollup-darwin-arm64': 4.57.0 + '@rollup/rollup-darwin-x64': 4.57.0 + '@rollup/rollup-freebsd-arm64': 4.57.0 + '@rollup/rollup-freebsd-x64': 4.57.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.0 + '@rollup/rollup-linux-arm-musleabihf': 4.57.0 + '@rollup/rollup-linux-arm64-gnu': 4.57.0 + '@rollup/rollup-linux-arm64-musl': 4.57.0 + '@rollup/rollup-linux-loong64-gnu': 4.57.0 + '@rollup/rollup-linux-loong64-musl': 4.57.0 + '@rollup/rollup-linux-ppc64-gnu': 4.57.0 + '@rollup/rollup-linux-ppc64-musl': 4.57.0 + '@rollup/rollup-linux-riscv64-gnu': 4.57.0 + '@rollup/rollup-linux-riscv64-musl': 4.57.0 + '@rollup/rollup-linux-s390x-gnu': 4.57.0 + '@rollup/rollup-linux-x64-gnu': 4.57.0 + '@rollup/rollup-linux-x64-musl': 4.57.0 + '@rollup/rollup-openbsd-x64': 4.57.0 + '@rollup/rollup-openharmony-arm64': 4.57.0 + '@rollup/rollup-win32-arm64-msvc': 4.57.0 + '@rollup/rollup-win32-ia32-msvc': 4.57.0 + '@rollup/rollup-win32-x64-gnu': 4.57.0 + '@rollup/rollup-win32-x64-msvc': 4.57.0 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + semver@6.3.1: {} + + set-cookie-parser@2.7.2: {} + + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + source-map-js@1.2.1: {} + + string-convert@0.2.1: {} + + stylis@4.3.6: {} + + svg-parser@2.0.4: {} + + tailwindcss@4.1.18: {} + + tapable@2.3.0: {} + + throttle-debounce@5.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tslib@2.8.1: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + + vite-plugin-svgr@4.5.0(rollup@4.57.0)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2)): + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.57.0) + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) + vite: 7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2) + transitivePeerDependencies: + - rollup + - supports-color + - typescript + + vite@7.3.1(@types/node@20.19.30)(jiti@2.6.1)(lightningcss@1.30.2): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.30 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + + yallist@3.1.1: {} diff --git a/helix-web/public/vite.svg b/helix-web/public/vite.svg new file mode 100644 index 0000000..a8bc145 --- /dev/null +++ b/helix-web/public/vite.svg @@ -0,0 +1,21 @@ + diff --git a/helix-web/src/api/auth/index.ts b/helix-web/src/api/auth/index.ts new file mode 100644 index 0000000..f3fb84f --- /dev/null +++ b/helix-web/src/api/auth/index.ts @@ -0,0 +1,68 @@ +import webClient from "@/client/web-client" +import { HttpStatus } from "@/constant" +import type { CaptchaResponse, MfaAuthResponse, UserAuthResponse } from "@/types/web/response" +import type { UsernamePasswordLoginRequest } from "@/types/web/request" + +/** + * 获取验证码图片及验证码 UUID + */ +async function getCaptcha(): Promise { + const { data, status } = await webClient.get("/captcha") + if (status == HttpStatus.OK) { + return data as CaptchaResponse + } else { + return null + } +} + +/** + * 使用用户名密码登录 + * @param request 用户名密码 + */ +async function usernamePasswordLogin( + request: UsernamePasswordLoginRequest +): Promise { + const { data } = await webClient.post("/auth/login", request) + return data +} + +/** + * 使用企业微信登录 + * @param code 由企业微信提供的身份验证 code + */ +async function wecomLogin(code: string): Promise { + const urlSearchParams = new URLSearchParams() + urlSearchParams.append("code", code) + + + const { data } = await webClient.get(`/auth/wecom/login?${urlSearchParams.toString()}`) + return data +} + +/** + * 使用 Microsoft Entra 登录 + * @param msalToken 由 Microsoft Entra 提供的用户身份令牌 + */ +async function msalLogin(msalToken: string): Promise { + const { data, headers } = await webClient.post(`/auth/msal/login`, { + msalToken, + }) + + const token = (headers as Record).authorization ?? "" + + if (!token) { + return Promise.reject(new Error("未获取到身份令牌,登录失败")) + } + + return data +} + +/** + * 获取注册功能是否启用 + */ +async function fetchRegisterEnabled() { + const { data } = await webClient.get("/auth/register-enabled") + return data +} + +export { usernamePasswordLogin, wecomLogin, msalLogin, getCaptcha, fetchRegisterEnabled } diff --git a/helix-web/src/api/department/index.ts b/helix-web/src/api/department/index.ts new file mode 100644 index 0000000..3d31c2a --- /dev/null +++ b/helix-web/src/api/department/index.ts @@ -0,0 +1,13 @@ +import webClient from "@/client/web-client" +import type { TreeNode } from "@/types/tree" +import type { Department } from "@/types/entity" + +export async function fetchDepartmentTree(): Promise> { + const { data } = await webClient.get>("/departments/tree") + return data +} + +export async function fetchDepartments(): Promise { + const { data } = await webClient.get("/departments") + return data +} diff --git a/helix-web/src/api/index.ts b/helix-web/src/api/index.ts new file mode 100644 index 0000000..5c1d525 --- /dev/null +++ b/helix-web/src/api/index.ts @@ -0,0 +1,6 @@ +export * as AuthApi from "./auth" +export * as MenuApi from "./menu" +export * as DeptApi from "./department" +export * as UserApi from "./user" +export * as PositionApi from "./position" +export * as RoleApi from "./role" diff --git a/helix-web/src/api/menu/index.ts b/helix-web/src/api/menu/index.ts new file mode 100644 index 0000000..358034e --- /dev/null +++ b/helix-web/src/api/menu/index.ts @@ -0,0 +1,8 @@ +import webClient from "@/client/web-client" +import type { TreeNode } from "@/types/tree" +import type { MenuItem } from "@/types/entity" + +export async function fetchMenuTree() { + const { data } = await webClient.get[]>("/menus") + return data +} diff --git a/helix-web/src/api/position/index.ts b/helix-web/src/api/position/index.ts new file mode 100644 index 0000000..0c56c3b --- /dev/null +++ b/helix-web/src/api/position/index.ts @@ -0,0 +1,9 @@ +import webClient from "@/client/web-client" +import type { QueryPositionRequest } from "@/types/web/request" +import type { PageResponse } from "@/types/web/response" +import type { Position } from "@/types/entity" + +export async function fetchPositions(request: QueryPositionRequest): Promise> { + const { data } = await webClient.get>("/positions") + return data +} diff --git a/helix-web/src/api/role/index.ts b/helix-web/src/api/role/index.ts new file mode 100644 index 0000000..820565c --- /dev/null +++ b/helix-web/src/api/role/index.ts @@ -0,0 +1,39 @@ +import type { QueryRoleRequest } from "@/types/web/request" +import webClient from "@/client/web-client" +import type { PageResponse, RoleResponse } from "@/types/web/response" +import type { RoleFormValues } from "@/components/role-display-form" + +export async function fetchRoles( + request: QueryRoleRequest | null +): Promise { + const params = new URLSearchParams() + params.append("pageNum", `${request?.pageNum ?? 1}`) + params.append("pageSize", `${request?.pageSize ?? 1}`) + + if (request?.name) { + params.append("name", request.name) + } + + if (request?.code) { + params.append("code", request.code) + } + + if (request?.status) { + params.append("status", request.status) + } + + const { data } = await webClient.get(`/roles?${params.toString()}`) + return data +} + +export async function addRole(request: RoleFormValues) { + return await webClient.post("/roles", request) +} + +export async function editRole(request: RoleFormValues) { + return await webClient.put("/roles", request) +} + +export async function deleteRole(id: number | string) { + return await webClient.delete(`/roles/${id}`) +} diff --git a/helix-web/src/api/user/index.ts b/helix-web/src/api/user/index.ts new file mode 100644 index 0000000..d40c343 --- /dev/null +++ b/helix-web/src/api/user/index.ts @@ -0,0 +1,88 @@ +import webClient from "@/client/web-client" +import { getCountryCallingCode } from "libphonenumber-js" +import { getDefaultCountryCode } from "@/utils/phone-number-utils" +import type { AddUserRequest, EditUserRequest, QueryUserRequest } from "@/types/web/request" +import type { PageResponse, UserDetailResponse } from "@/types/web/response" +import type { UserFormValues } from "@/types/form" + +export async function fetchUsers( + request?: QueryUserRequest +): Promise> { + const urlSearchParam = new URLSearchParams() + urlSearchParam.append("pageNum", `${request?.pageNum ?? 1}`) + urlSearchParam.append("pageSize", `${request?.pageSize ?? 10}`) + + if (request?.departmentId) { + urlSearchParam.append("departmentId", `${request.departmentId}`) + } + + if (request?.username) { + urlSearchParam.append("username", request.username) + } + + if (request?.regionAbbreviation) { + urlSearchParam.append("regionAbbreviation", request.regionAbbreviation) + } + + if (request?.phoneNumber) { + urlSearchParam.append("phoneNumber", request.phoneNumber) + } + + if (request?.status) { + urlSearchParam.append("status", request.status) + } + + if (request?.createdAtStart) { + urlSearchParam.append("createdAtStart", request.createdAtStart) + } + + if (request?.createdAtEnd) { + urlSearchParam.append("createdAtEnd", request.createdAtEnd) + } + + const { data } = await webClient.get>( + `/users?${urlSearchParam.toString()}` + ) + return data +} + +export async function fetchUserById(userId: number): Promise { + const { data } = await webClient.get(`/users/${userId}`) + return data +} + +export async function addUser(values: UserFormValues) { + await webClient.post("/users", { + username: values.username, + password: values.password, + fullName: values.fullName, + email: values.email, + regionAbbreviation: values.regionAbbreviation ?? getDefaultCountryCode(), + phoneNumber: values.phoneNumber, + avatarUrl: values.avatarUrl, + status: values.status, + departmentId: values.departmentId, + positionId: values.positionId, + roleIds: [], + } as AddUserRequest) +} + +export async function editUser(values: UserFormValues) { + await webClient.put("/users", { + id: values.id, + username: values.username, + fullName: values.fullName, + email: values.email, + regionAbbreviation: values.regionAbbreviation ?? getDefaultCountryCode(), + phoneNumber: values.phoneNumber, + avatarUrl: values.avatarUrl, + status: values.status, + departmentId: values.departmentId, + positionId: values.positionId, + roleIds: [], + } as EditUserRequest) +} + +export async function deleteUser(userId: number | string) { + await webClient.delete(`/users/${userId}`) +} diff --git a/helix-web/src/assets/dingtalk.svg b/helix-web/src/assets/dingtalk.svg new file mode 100644 index 0000000..97fb5a3 --- /dev/null +++ b/helix-web/src/assets/dingtalk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/discord.svg b/helix-web/src/assets/discord.svg new file mode 100644 index 0000000..fb73caa --- /dev/null +++ b/helix-web/src/assets/discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/email.svg b/helix-web/src/assets/email.svg new file mode 100644 index 0000000..f878707 --- /dev/null +++ b/helix-web/src/assets/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/gitlab.svg b/helix-web/src/assets/gitlab.svg new file mode 100644 index 0000000..e3c800e --- /dev/null +++ b/helix-web/src/assets/gitlab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/google.svg b/helix-web/src/assets/google.svg new file mode 100644 index 0000000..77cdb81 --- /dev/null +++ b/helix-web/src/assets/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/lark.svg b/helix-web/src/assets/lark.svg new file mode 100644 index 0000000..c26c6a7 --- /dev/null +++ b/helix-web/src/assets/lark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/logo.svg b/helix-web/src/assets/logo.svg new file mode 100644 index 0000000..e958d9c --- /dev/null +++ b/helix-web/src/assets/logo.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/helix-web/src/assets/microsoft.svg b/helix-web/src/assets/microsoft.svg new file mode 100644 index 0000000..89f6237 --- /dev/null +++ b/helix-web/src/assets/microsoft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/password.svg b/helix-web/src/assets/password.svg new file mode 100644 index 0000000..7ceee90 --- /dev/null +++ b/helix-web/src/assets/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/react.svg b/helix-web/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/helix-web/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/slack.svg b/helix-web/src/assets/slack.svg new file mode 100644 index 0000000..2514288 --- /dev/null +++ b/helix-web/src/assets/slack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/assets/wecom.svg b/helix-web/src/assets/wecom.svg new file mode 100644 index 0000000..91b6f30 --- /dev/null +++ b/helix-web/src/assets/wecom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/helix-web/src/client/web-client/index.ts b/helix-web/src/client/web-client/index.ts new file mode 100644 index 0000000..5ad7e95 --- /dev/null +++ b/helix-web/src/client/web-client/index.ts @@ -0,0 +1,38 @@ +import axios, { type AxiosError } from "axios" +import dayjs from "dayjs" +import store from "@/store" +import { HttpStatus } from "@/constant" +import { logout } from "@/store/auth-slice" +import type { GeneralErrorResponse } from "@/types/web/response" + +const webClient = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL, + timeout: dayjs.duration({ seconds: 10 }).asMilliseconds(), + withCredentials: true, + withXSRFToken: true, +}) + +webClient.interceptors.request.use( + (config) => { + return config + }, + (error: unknown) => { + console.log(error) + return Promise.reject(new Error("请求错误,请稍后再试")) + } +) + +webClient.interceptors.response.use( + (response) => { + return response + }, + (error: unknown) => { + const err = error as AxiosError + if (err.response?.status == HttpStatus.UNAUTHORIZED) { + store.dispatch(logout()) + } + return Promise.reject(error as AxiosError) + } +) + +export default webClient diff --git a/helix-web/src/components/add-role-dialogue/index.tsx b/helix-web/src/components/add-role-dialogue/index.tsx new file mode 100644 index 0000000..dfa3d13 --- /dev/null +++ b/helix-web/src/components/add-role-dialogue/index.tsx @@ -0,0 +1,24 @@ +import type { FormInstance } from "antd" +import RoleDisplayForm, { type RoleFormValues } from "@/components/role-display-form" + +export interface AddRoleDialogueProps { + form: FormInstance +} + +export default function AddRoleDialogue({ form }: AddRoleDialogueProps) { + return ( + + ) +} diff --git a/helix-web/src/components/add-user-dialogue/index.tsx b/helix-web/src/components/add-user-dialogue/index.tsx new file mode 100644 index 0000000..f0d7898 --- /dev/null +++ b/helix-web/src/components/add-user-dialogue/index.tsx @@ -0,0 +1,30 @@ +import { type FormInstance } from "antd" +import UserDisplayForm from "@/components/user-display-form" +import type { UserFormValues } from "@/types/form" + +interface AddUserProps { + form: FormInstance +} + +export default function AddUserDialogue({ form }: AddUserProps) { + return ( + + ) +} diff --git a/helix-web/src/components/edit-role-dialogue/index.tsx b/helix-web/src/components/edit-role-dialogue/index.tsx new file mode 100644 index 0000000..e1db88b --- /dev/null +++ b/helix-web/src/components/edit-role-dialogue/index.tsx @@ -0,0 +1,12 @@ +import type { FormInstance } from "antd" +import RoleDisplayForm, { type RoleFormValues } from "@/components/role-display-form" +import type { Role } from "@/types/entity" + +export interface EditRoleDialogueProps { + form: FormInstance + initialValues: RoleFormValues +} + +export default function EditRoleDialogue({ form, initialValues }: EditRoleDialogueProps) { + return +} diff --git a/helix-web/src/components/edit-user-dialogue/index.tsx b/helix-web/src/components/edit-user-dialogue/index.tsx new file mode 100644 index 0000000..2703e62 --- /dev/null +++ b/helix-web/src/components/edit-user-dialogue/index.tsx @@ -0,0 +1,22 @@ +import type { FormInstance } from "antd" +import UserDisplayForm from "@/components/user-display-form" +import type { User } from "@/types/entity" +import type { UserFormValues } from "@/types/form" + +interface EditUserProps { + user: User + form: FormInstance +} + +export default function EditUserDialogue({ user, form }: EditUserProps) { + return ( + + ) +} diff --git a/helix-web/src/components/icon/index.tsx b/helix-web/src/components/icon/index.tsx new file mode 100644 index 0000000..192dc40 --- /dev/null +++ b/helix-web/src/components/icon/index.tsx @@ -0,0 +1,99 @@ +import type { ForwardRefExoticComponent } from "react" +import Icon from "@ant-design/icons" + +import BrandMicrosoft from "@/assets/microsoft.svg?react" +import BrandDingTalk from "@/assets/dingtalk.svg?react" +import BrandWeCom from "@/assets/wecom.svg?react" +import BrandLark from "@/assets/lark.svg?react" +import BrandSlack from "@/assets/slack.svg?react" +import BrandGoogle from "@/assets/google.svg?react" +import BrandGitlab from "@/assets/gitlab.svg?react" +import BrandEmail from "@/assets/email.svg?react" +import BrandDiscord from "@/assets/discord.svg?react" +import Logo from "@/assets/logo.svg?react" +import type { AntIconComponentProps } from "@/types/antd" + +/** + * Microsoft 图标 + * @param props Icon 属性 + * @constructor + */ +export function MicrosoftFilled(props: Partial) { + return } {...props} /> +} + +/** + * 钉钉图标 + * @param props Icon 属性 + * @constructor + */ +export function DingTalkFilled(props: Partial) { + return } {...props} /> +} + +/** + * 企业微信图标 + * @param props Icon 属性 + * @constructor + */ +export function WeComFilled(props: Partial) { + return } {...props} /> +} + +/** + * 飞书图标 + * @param props Icon 属性 + * @constructor + */ +export function LarkFilled(props: Partial) { + return } {...props} /> +} + +/** + * Slack 图标 + * @param props Icon 属性 + * @constructor + */ +export function SlackFilled(props: Partial) { + return } {...props} /> +} + +/** + * Google 图标 + * @param props Icon 属性 + * @constructor + */ +export function GoogleFilled(props: Partial) { + return } {...props} /> +} + +/** + * GitLab 图标 + * @param props Icon 属性 + * @constructor + */ +export function GitlabFilled(props: Partial) { + return } {...props} /> +} + +/** + * Email 图标 + * @param props Icon 属性 + * @constructor + */ +export function EmailFilled(props: Partial) { + return } {...props} /> +} + +/** + * Discord 图标 + * @param props Icon 属性 + * @constructor + */ +export function DiscordFilled(props: Partial) { + return } {...props} /> +} + +export function ApplicationLogo(props: Partial) { + return } {...props} /> +} diff --git a/helix-web/src/components/loading-fallback/index.tsx b/helix-web/src/components/loading-fallback/index.tsx new file mode 100644 index 0000000..fc1adb9 --- /dev/null +++ b/helix-web/src/components/loading-fallback/index.tsx @@ -0,0 +1,5 @@ +import { Spin } from "antd" + +export default function LoadingFallback() { + return +} diff --git a/helix-web/src/components/protected-route/index.tsx b/helix-web/src/components/protected-route/index.tsx new file mode 100644 index 0000000..b93b92b --- /dev/null +++ b/helix-web/src/components/protected-route/index.tsx @@ -0,0 +1,22 @@ +import { Navigate, Outlet, useLocation } from "react-router" +import { useAppSelector } from "@/store" +import DashboardLayout from "@/layouts/dashboard-layout" + +/** + * 需要身份验证的前置组件 + * @constructor + */ +export default function ProtectedRoute() { + const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated) + const location = useLocation() + + if (!isAuthenticated) { + return + } + + return ( + + + + ) +} diff --git a/helix-web/src/components/role-display-form/index.tsx b/helix-web/src/components/role-display-form/index.tsx new file mode 100644 index 0000000..97837a0 --- /dev/null +++ b/helix-web/src/components/role-display-form/index.tsx @@ -0,0 +1,82 @@ +import { App, Form, type FormInstance, Input, InputNumber, Select, Switch } from "antd" +import { type Status, StatusOptions } from "@/types/constant" +import type { FormMode } from "@/types/form" +import { useEffect, useMemo } from "react" + +/** + * Role form values. + */ +export interface RoleFormValues { + id: number | string | null + name: string + code: string + sort: number + defaultValue: boolean + description: string | null + status: Status +} + +/** + * Component props. + */ +export interface RoleDisplayFormProps { + initialValues?: RoleFormValues + form: FormInstance + mode: FormMode +} + +export default function RoleDisplayForm({ initialValues, form, mode }: RoleDisplayFormProps) { + const isEditing = useMemo(() => mode == "edit", [mode]) + + // Initialise form values + useEffect(() => { + if (initialValues) { + form.setFieldsValue(initialValues) + } else { + form.resetFields() + } + }, [initialValues, form]) + + return ( + + form={form} + initialValues={initialValues} + layout="vertical" + labelAlign="right" + validateTrigger="onBlur"> + label="角色编号" hidden={!isEditing} name="id"> + + + + label="角色名称" + name="name" + rules={[{ required: true, message: "角色名称不能为空" }]}> + + + + label="角色编码" + name="code" + rules={[ + { required: true, message: "角色编码不能为空" }, + { pattern: /^[a-z-]+$/, message: "角色编码格式错误,仅支持小写英文字母及 '-'" }, + ]}> + + + + label="排序编码" + name="sort" + rules={[ + { required: true, message: "排序不能为空" }, + { type: "number", min: 0, max: Number.MAX_VALUE, message: "排序必须是正数" }, + ]}> + + + label="是否为默认角色" name="defaultValue"> + + + label="角色状态" name="status"> + options={StatusOptions} /> + + + ) +} diff --git a/helix-web/src/components/user-display-form/index.tsx b/helix-web/src/components/user-display-form/index.tsx new file mode 100644 index 0000000..69225bf --- /dev/null +++ b/helix-web/src/components/user-display-form/index.tsx @@ -0,0 +1,200 @@ +import { useEffect, useMemo, useState } from "react" +import { App, Checkbox, Form, type FormInstance, Input, Select, Space, TreeSelect } from "antd" +import type { AxiosError } from "axios" +import { getCountries, getCountryCallingCode } from "libphonenumber-js" +import { DeptApi, PositionApi } from "@/api" +import type { UserFormValues } from "@/types/form" +import type { Department, Position } from "@/types/entity" +import type { GeneralErrorResponse } from "@/types/web/response" +import type { AntTreeSelectOption } from "@/types/antd" + +interface UserDisplayFormProps { + initialValues?: UserFormValues + isEditing: boolean + isAdding?: boolean + form: FormInstance +} + +const countryOptions = getCountries().map((country) => { + const callingCode = getCountryCallingCode(country) + return { + label: `${country} (+${callingCode})`, + value: country, + key: country, + } +}) + +function transformDepartmentToTree(departments: Department[]): AntTreeSelectOption[] { + const sortedDepartments = [...departments].sort((a, b) => a.sort - b.sort) + + const map: Record> = {} + const tree: AntTreeSelectOption[] = [] + + sortedDepartments.forEach((dept) => { + map[dept.id] = { + label: dept.name, + value: dept.id, + children: [], + } + }) + + sortedDepartments.forEach((dept) => { + const node = map[dept.id] + if (dept.parentId !== null && map[dept.parentId]) { + map[dept.parentId].children!.push(node) + } else { + tree.push(node) + } + }) + return tree +} + +export default function UserDisplayForm({ + initialValues, + isEditing, + form, + isAdding = false, +}: UserDisplayFormProps) { + // Get message wrapper from Antd + const { message } = App.useApp() + + // Build form values. + const initialFormValues: UserFormValues | undefined = initialValues + ? ({ + ...initialValues, + password: "", + } as UserFormValues) + : undefined + + // Department state used to save all departments + const [departments, setDepartments] = useState([]) + // Position state used to save all positions + const [positions, setPositions] = useState([]) + + // Departments options + const departmentOptions = useMemo(() => { + return transformDepartmentToTree(departments) + }, [departments]) + + const positionOptions = useMemo(() => { + return positions.map((position) => ({ + label: position.name, + value: position.id, + key: position.id, + })) + }, [positions]) + + // Initialise form values + useEffect(() => { + if (initialFormValues) { + form.setFieldsValue(initialFormValues) + } else { + form.resetFields() + } + }, [initialValues, form]) + + // Initialise department data on component mounted + useEffect(() => { + const fetchDepartmentsFuture = DeptApi.fetchDepartments() + const fetchPositionsFuture = PositionApi.fetchPositions({ pageNum: 1, pageSize: 999 }) + + Promise.all([fetchDepartmentsFuture, fetchPositionsFuture]) + .then(([departments, positionPage]) => { + setDepartments(departments) + setPositions(positionPage.content) + }) + .catch((error: unknown) => { + const err = error as AxiosError + void message.error(err.response?.data.message ?? "获取部门或岗位数据失败,请稍后再试") + }) + }, []) + + return ( + + form={form} + initialValues={initialFormValues} + layout="vertical" + labelAlign="right" + disabled={!isEditing}> + label="用户 ID" name="id" hidden={isAdding}> + + + + + label="全名" + name="fullName" + rules={[{ required: true, message: "用户全名不能为空" }]}> + + + + + label="用户名" + name="username" + rules={[{ required: true, message: "请输入用户名" }]}> + + + + {isAdding && ( + + label="密码" + name="password" + rules={[{ required: true, message: "请输入密码" }]}> + + + )} + + + label="电子邮箱" + name="email" + rules={[ + { type: "email", message: "邮箱格式不正确" }, + { required: true, message: "邮箱不能为空" }, + ]}> + + + + label="联系电话" required> + + + noStyle + name="regionAbbreviation" + rules={[{ required: true, message: "请选择国际电信区域码" }]}> + + + + + + + label="用户状态" + name="status" + rules={[{ required: true, message: "用户状态不能为空" }]}> + + + + ) +} diff --git a/helix-web/src/config/dayjs-config/index.ts b/helix-web/src/config/dayjs-config/index.ts new file mode 100644 index 0000000..b48e603 --- /dev/null +++ b/helix-web/src/config/dayjs-config/index.ts @@ -0,0 +1,11 @@ +// 配置插件 +import dayjs from "dayjs" + +import duration from "dayjs/plugin/duration" +dayjs.extend(duration) + +// 配置语言 +import "dayjs/locale/zh-cn" +dayjs.locale("zh-cn") + +console.log("Global Dayjs plugins initialised.") diff --git a/helix-web/src/config/msal-config/index.ts b/helix-web/src/config/msal-config/index.ts new file mode 100644 index 0000000..81bfabb --- /dev/null +++ b/helix-web/src/config/msal-config/index.ts @@ -0,0 +1,17 @@ +import { type Configuration, PublicClientApplication } from "@azure/msal-browser" + +const clientId = import.meta.env.VITE_MSAL_CLIENT_ID +const tenantId = import.meta.env.VITE_MSAL_TENANT_ID + +const msalConfig: Configuration = { + auth: { + clientId, + authority: `https://login.microsoftonline.com/${tenantId}`, + redirectUri: "http://localhost:5173" + }, + cache: { + cacheLocation: "localStorage", + } +} + +export const msalInstance = new PublicClientApplication(msalConfig) \ No newline at end of file diff --git a/helix-web/src/constant/http-status/index.ts b/helix-web/src/constant/http-status/index.ts new file mode 100644 index 0000000..8d79423 --- /dev/null +++ b/helix-web/src/constant/http-status/index.ts @@ -0,0 +1,66 @@ +/** + * HTTP 状态码 + */ +export const HttpStatus = { + CONTINUE: 100, + SWITCHING_PROTOCOLS: 101, + PROCESSING: 102, + EARLY_HINTS: 103, + OK: 200, + CREATED: 201, + ACCEPTED: 202, + NON_AUTHORITATIVE_INFORMATION: 203, + NO_CONTENT: 204, + RESET_CONTENT: 205, + PARTIAL_CONTENT: 206, + MULTI_STATUS: 207, + ALREADY_REPORTED: 208, + IM_USED: 226, + MULTIPLE_CHOICES: 300, + MOVED_PERMANENTLY: 301, + FOUND: 302, + SEE_OTHER: 303, + NOT_MODIFIED: 304, + USE_PROXY: 305, + TEMPORARY_REDIRECT: 307, + PERMANENT_REDIRECT: 308, + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + PAYMENT_REQUIRED: 402, + FORBIDDEN: 403, + NOT_FOUND: 404, + METHOD_NOT_ALLOWED: 405, + NOT_ACCEPTABLE: 406, + PROXY_AUTHENTICATION_REQUIRED: 407, + REQUEST_TIMEOUT: 408, + CONFLICT: 409, + GONE: 410, + LENGTH_REQUIRED: 411, + PRECONDITION_FAILED: 412, + PAYLOAD_TOO_LARGE: 413, + URI_TOO_LONG: 414, + UNSUPPORTED_MEDIA_TYPE: 415, + RANGE_NOT_SATISFIABLE: 416, + EXPECTATION_FAILED: 417, + I_AM_A_TEAPOT: 418, + UNPROCESSABLE_ENTITY: 422, + LOCKED: 423, + FAILED_DEPENDENCY: 424, + TOO_EARLY: 425, + UPGRADE_REQUIRED: 426, + PRECONDITION_REQUIRED: 428, + TOO_MANY_REQUESTS: 429, + REQUEST_HEADER_FIELDS_TOO_LARGE: 431, + UNAVAILABLE_FOR_LEGAL_REASONS: 451, + INTERNAL_SERVER_ERROR: 500, + NOT_IMPLEMENTED: 501, + BAD_GATEWAY: 502, + SERVICE_UNAVAILABLE: 503, + GATEWAY_TIMEOUT: 504, + HTTP_VERSION_NOT_SUPPORTED: 505, + VARIANT_ALSO_NEGOTIATES: 506, + INSUFFICIENT_STORAGE: 507, + LOOP_DETECTED: 508, + NOT_EXTENDED: 510, + NETWORK_AUTHENTICATION_REQUIRED: 511, +} diff --git a/helix-web/src/constant/index.ts b/helix-web/src/constant/index.ts new file mode 100644 index 0000000..4b4f38e --- /dev/null +++ b/helix-web/src/constant/index.ts @@ -0,0 +1 @@ +export { HttpStatus } from "./http-status" diff --git a/helix-web/src/hooks/index.tsx b/helix-web/src/hooks/index.tsx new file mode 100644 index 0000000..bf22bea --- /dev/null +++ b/helix-web/src/hooks/index.tsx @@ -0,0 +1,20 @@ +import { useMatches } from "react-router" +import type { BreadcrumbItemType } from "antd/es/breadcrumb/Breadcrumb" +import type { RouteHandle } from "@/types/route" + +export function useAntBreadcrumbs(): BreadcrumbItemType[] { + const matches = useMatches() + + return matches + .filter((match) => (match.handle as RouteHandle)?.label) + .map((match) => { + const handle = match.handle as RouteHandle + + const path = match.pathname + const title = handle.label || "" + + return { + title, + } + }) +} diff --git a/helix-web/src/index.css b/helix-web/src/index.css new file mode 100644 index 0000000..8e47a54 --- /dev/null +++ b/helix-web/src/index.css @@ -0,0 +1,13 @@ +@layer theme, base, antd, components, utilities; +@import "tailwindcss"; + +html, body { + margin: 0; + padding: 0; + height: 100vh; + overflow: hidden; +} + +#root { + height: 100%; +} diff --git a/helix-web/src/layouts/dashboard-layout/index.tsx b/helix-web/src/layouts/dashboard-layout/index.tsx new file mode 100644 index 0000000..a1b313b --- /dev/null +++ b/helix-web/src/layouts/dashboard-layout/index.tsx @@ -0,0 +1,145 @@ +import { type ReactNode, useEffect, useMemo, useState } from "react" +import { Link, Navigate, useNavigate } from "react-router" +import { App, Avatar, Breadcrumb, Dropdown, Layout, Menu, type MenuProps, Space } from "antd" +import { DownOutlined } from "@ant-design/icons" +import { ApplicationLogo } from "@/components/icon" +import { useAppDispatch, useAppSelector } from "@/store" +import { useAntBreadcrumbs } from "@/hooks" +import { logout } from "@/store/auth-slice" +import { MenuApi } from "@/api" +import axios from "axios" +import type { TreeNode } from "@/types/tree" +import type { MenuItem } from "@/types/entity" +import { AppUtils } from "@/utils" +import type { GeneralErrorResponse } from "@/types/web/response" + +type AntMenuItem = Required["items"][number] + +export interface DashboardLayoutProps { + children: ReactNode +} + +function transformMenuData(nodes: TreeNode[]): AntMenuItem[] { + if (!nodes || nodes.length === 0) { + return [] + } + + return nodes + .sort((a, b) => a.item.sort - b.item.sort) + .map((node) => { + const { item, children } = node + const hasChildren = children && children.length > 0 + + if (item.isVisible) { + const menuItem: AntMenuItem = { + key: item.code, + label: item.name, + } + + if (item.path) { + if (item.isExternalLink) { + menuItem.extra = + } else { + menuItem.extra = + } + } + + if (hasChildren) { + // Append children + return { ...menuItem, children: transformMenuData(children) } + } + + return menuItem + } + return null + }) +} + +export default function DashboardLayout({ children }: DashboardLayoutProps) { + const { modal, message } = App.useApp() + const user = useAppSelector((store) => store.auth.user!) + const dispatch = useAppDispatch() + const breadcrumbItems = useAntBreadcrumbs() + const navigate = useNavigate() + + const onLogout = ({ key }: { key: string }) => { + if (key == "logout") { + modal.confirm({ + title: "确定要注销吗?", + okText: "确定", + cancelText: "取消", + onOk: () => { + dispatch(logout()) + void navigate("/login") + }, + maskClosable: false, + keyboard: false, + }) + } + } + + const [menuItems, setMenuItems] = useState([]) + + useEffect(() => { + MenuApi.fetchMenuTree() + .then((response) => { + setMenuItems(transformMenuData(response)) + }) + .catch((error: unknown) => { + console.log(error) + const errorMessage = + axios.isAxiosError(error) && error.response?.data.message + ? error.response?.data.message + : "无法读取菜单数据" + void message.error({ + content: errorMessage, + }) + }) + }, []) + + const dropDownMenuItems: MenuProps["items"] = [ + { + key: "logout", + danger: true, + label: "注销", + }, + ] + + const appTitle = useMemo(() => AppUtils.getAppTitle(), []) + + return ( + + +
+ + {appTitle} +
+ +
+ + + {user.fullName} + + + + +
+
+ + + + + + + {children} + + + + ) +} diff --git a/helix-web/src/main.tsx b/helix-web/src/main.tsx new file mode 100644 index 0000000..aa00697 --- /dev/null +++ b/helix-web/src/main.tsx @@ -0,0 +1,32 @@ +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import { Provider as ReduxProvider } from "react-redux" +import { PersistGate } from "redux-persist/integration/react" +import { RouterProvider } from "react-router" +import { App as AntApp, ConfigProvider as AntConfigProvider } from "antd" +import { StyleProvider } from "@ant-design/cssinjs" +import AntSimplifiedChinese from "antd/locale/zh_CN" +import "./index.css" +import "@/config/dayjs-config" +import store, { persistor } from "@/store" +import router from "@/router" + +createRoot(document.getElementById("root")!).render( + + + + + + + + + + + + + +) diff --git a/helix-web/src/page/error/index.tsx b/helix-web/src/page/error/index.tsx new file mode 100644 index 0000000..8cadb5f --- /dev/null +++ b/helix-web/src/page/error/index.tsx @@ -0,0 +1,33 @@ +import React from "react" +import { Link } from "react-router-dom" + +/** + * 携带错误信息的通用错误展示页 + * + * @param message 自定义的错误信息,默认为`发生了未知错误,请稍后再试` + * @constructor + */ +export default function ErrorPage({ message = "发生了未知错误,请稍后再试" }) { + return ( +
+
+
+
+
+

⚠️

+

+ 系统出现了一些异常。 +

+

{message}

+

我们已经知晓该问题并正在进行处理。

+ + 返回主页 + +
+
+ ) +} diff --git a/helix-web/src/page/home/index.tsx b/helix-web/src/page/home/index.tsx new file mode 100644 index 0000000..cec1b11 --- /dev/null +++ b/helix-web/src/page/home/index.tsx @@ -0,0 +1,7 @@ +import { useAppSelector } from "@/store" + +export default function HomePage() { + const user = useAppSelector((store) => store.auth.user!) + + return <>Welcome to Helix, {user.fullName} +} diff --git a/helix-web/src/page/login/index.tsx b/helix-web/src/page/login/index.tsx new file mode 100644 index 0000000..122ac8b --- /dev/null +++ b/helix-web/src/page/login/index.tsx @@ -0,0 +1,373 @@ +import { type MouseEvent, useCallback, useEffect, useState } from "react" +import { Link, useNavigate } from "react-router" +import { Form, Input, Button, Card, message, Divider, App } from "antd" +import dayjs from "dayjs" +import type { AxiosError } from "axios" +import { GithubFilled } from "@ant-design/icons" +import { useMsal } from "@azure/msal-react" +import { AuthApi } from "@/api" +import { useAppDispatch, useAppSelector } from "@/store" +import { loginSuccess, updateRegistrationEnabled } from "@/store/auth-slice" +import { + DingTalkFilled, + GoogleFilled, + LarkFilled, + MicrosoftFilled, + SlackFilled, + WeComFilled, + GitlabFilled, + DiscordFilled, + EmailFilled, +} from "@/components/icon" +import { fetchRegisterEnabled } from "@/api/auth" +import type { UsernamePasswordLoginRequest } from "@/types/web/request" +import type { CaptchaResponse, GeneralErrorResponse } from "@/types/web/response" + +export default function LoginPage() { + const registrationEnabled = useAppSelector((state) => state.auth.registrationEnabled) + const dispatch = useAppDispatch() + const navigate = useNavigate() + + const { message } = App.useApp() + const [form] = Form.useForm() + + // const [isLoading, setIsLoading] = useState(false) + const [hasCaptcha, setHasCaptcha] = useState(false) + const [captchaData, setCaptchaData] = useState() + + const fetchCaptcha = useCallback(async () => { + try { + const response = await AuthApi.getCaptcha() + if (response) { + setHasCaptcha(true) + setCaptchaData(response) + form.setFieldValue("uuid", response.uuid) + } else { + setHasCaptcha(false) + setCaptchaData(null) + form.setFieldValue("uuid", null) + } + } catch (error) { + console.error("Failed to fetch captcha due to an error:", error) + setHasCaptcha(false) + setCaptchaData(null) + form.setFieldValue("uuid", null) + } + }, [form]) + + useEffect(() => { + void fetchCaptcha() + }, [fetchCaptcha]) + + useEffect(() => { + void fetchRegisterEnabled().then((enabled) => { + dispatch(updateRegistrationEnabled(enabled)) + }) + }, [dispatch]) + + /** + * 用户名密码登录 + */ + const performLogin = useCallback( + async (values: UsernamePasswordLoginRequest) => { + try { + console.log("Login values:", values) + + const loginResponse = await AuthApi.usernamePasswordLogin(values) + + if (loginResponse == null) { + message.error("登录失败:服务器响应异常。") + return + } + + if ('mfaToken' in loginResponse) { + + } else { + dispatch( + loginSuccess({ + user: loginResponse + }) + ) + message.success("登录成功", dayjs.duration({ seconds: 3 }).asSeconds()) + await navigate("/") + } + } catch (errorInfo: unknown) { + const error = errorInfo as AxiosError + console.log(error) + message.error( + error.response?.data.message ?? "登录失败,请稍后再试", + dayjs.duration({ seconds: 3 }).asSeconds() + ) + } + }, + [dispatch, navigate] + ) + + /** + * 刷新验证码图片 + */ + const refreshCaptcha = (event: MouseEvent) => { + event.preventDefault() + void fetchCaptcha() + } + + /** + * 使用 Microsoft Entra ID 登录 + */ + const performMsalLogin = () => { + console.log("使用 Microsoft 账号登录") + // void doMsalLogin(msalContext.instance, dispatch, () => void navigate("/")) + } + + /** + * 使用 DingTalk 登录 + */ + const performDingTalkLogin = () => { + console.log("使用钉钉登录") + // todo implement this + } + + /** + * 使用 WeCom 登录 + */ + const performWeComLogin = () => { + console.log("使用企业微信登录") + // todo implement this + } + + /** + * 使用 Lark 登录 + */ + const performLarkLogin = () => { + console.log("使用飞书登录") + // todo implement this + } + + /** + * 使用 Slack 登录 + */ + const performSlackLogin = () => { + console.log("使用 Slack 登录") + // todo implement this + } + + /** + * 使用 Github 登录 + */ + const performGithubLogin = () => { + console.log("使用 GitHub 登录") + // todo implement this + } + + /** + * 使用 Google 登录 + */ + const performGoogleLogin = () => { + console.log("使用 Google 登录") + // todo implement this + } + + /** + * 使用 Gitlab 登录 + */ + const performGitlabLogin = () => { + console.log("使用 Gitlab 登录") + // todo implement this + } + + /** + * 使用 Discord 登录 + */ + const performDiscordLogin = () => { + console.log("使用 Discord 登录") + // todo implement this + } + + /** + * 使用 Email 登录 + */ + const performEmailLogin = () => { + console.log("使用 Email 登录") + // todo implement this + } + + return ( +
+ {/* 背景装饰元素 */} +
+
+
+ + +

欢迎回来

+

请登录您的账户

+
+ } + className="w-full max-w-md shadow-2xl border-0 backdrop-blur-sm bg-white/90 relative z-10" + styles={{ + body: { + padding: "32px", + }, + }}> + + name="usernamePasswordLoginForm" + form={form} + onFinish={(values) => { + void performLogin(values) + }} + layout="vertical" + className="space-y-4"> + + label={用户名} + name="username" + rules={[{ required: true, message: "请输入用户名!" }]}> + + + + + label={密码} + name="password" + rules={[{ required: true, message: "请输入密码!" }]}> + + + + {hasCaptcha ? ( + <> + + label={验证码} + name="captcha" + rules={[{ required: true, message: "请输入验证码!" }]}> +
+ + {captchaData?.captcha && ( + 验证码 + )} +
+ + name="uuid" hidden> +
Placeholder
+ + + ) : null} + + + + + + + {registrationEnabled ? ( +
+ 还没有账号?立即注册 +
+ ) : null} + + + 第三方帐号登录 + + +
+
+ +
+

© 2024 Your Company. All rights reserved.

+
+ + + ) +} diff --git a/helix-web/src/page/menu/index.tsx b/helix-web/src/page/menu/index.tsx new file mode 100644 index 0000000..d466fd1 --- /dev/null +++ b/helix-web/src/page/menu/index.tsx @@ -0,0 +1,7 @@ +export default function MenuPage() { + return ( + <> +
菜单管理
+ + ) +} diff --git a/helix-web/src/page/not-found/index.tsx b/helix-web/src/page/not-found/index.tsx new file mode 100644 index 0000000..664bb41 --- /dev/null +++ b/helix-web/src/page/not-found/index.tsx @@ -0,0 +1,30 @@ +import { Link } from "react-router" + +/** + * General page not found page. + * @constructor + */ +export default function NotFoundPage() { + return ( +
+
+
+
+ +
+

404

+

找不到页面

+

+ 找不到您需要的页面,您是否输入了错误的地址? +

+ + 返回主页 + +
+
+ ) +} diff --git a/helix-web/src/page/register/index.tsx b/helix-web/src/page/register/index.tsx new file mode 100644 index 0000000..fcfe59c --- /dev/null +++ b/helix-web/src/page/register/index.tsx @@ -0,0 +1,49 @@ +import { useEffect } from "react" +import { useNavigate } from "react-router" +import dayjs from "dayjs" +import type { AxiosError } from "axios" +import { message } from "antd" + +import { AuthApi } from "@/api" +import type { GeneralErrorResponse } from "@/types/web/response" + +export default function RegisterPage() { + const [messageApi, contextHolder] = message.useMessage() + const navigate = useNavigate() + + useEffect(() => { + let cancelled = false + + AuthApi.fetchRegisterEnabled() + .then((enabled) => { + if (!enabled && !cancelled) { + void messageApi + .error("注册功能暂未开放", dayjs.duration({ seconds: 3 }).asSeconds()) + .then(() => { + if (!cancelled) { + void navigate("/login") + } + }) + } + }) + .catch((reason: unknown) => { + if (cancelled) return + const error = reason as AxiosError + void messageApi.error(error.response?.data.message ?? "发生未知错误,请稍后再试") + }) + + return () => { + cancelled = true + } + }, [messageApi, navigate]) + + return ( +
+ {contextHolder} +
+
+
+
注册页
+
+ ) +} diff --git a/helix-web/src/page/role/index.tsx b/helix-web/src/page/role/index.tsx new file mode 100644 index 0000000..408d49e --- /dev/null +++ b/helix-web/src/page/role/index.tsx @@ -0,0 +1,320 @@ +import { useEffect, useState } from "react" +import axios, { type AxiosError } from "axios" +import { App, Button, Form, Input, Select, Space, Switch, Table } from "antd" +import type { Role } from "@/types/entity" +import { RoleApi } from "@/api" +import type { QueryRoleRequest } from "@/types/web/request" +import type { GeneralErrorResponse } from "@/types/web/response" +import { + DeleteOutlined, + ExportOutlined, + ImportOutlined, + PlusOutlined, + SearchOutlined, + UndoOutlined, +} from "@ant-design/icons" +import type { QueryRoleForm } from "@/types/form" +import type { Status } from "@/types/constant" +import AddRoleDialogue from "@/components/add-role-dialogue" +import type { RoleFormValues } from "@/components/role-display-form" +import EditRoleDialogue from "@/components/edit-role-dialogue" + +export default function RolePage() { + const { message, modal } = App.useApp() + + const [queryForm] = Form.useForm() + const [addRoleForm] = Form.useForm() + const [editRoleForm] = Form.useForm() + const [roles, setRoles] = useState([]) + const [pageNum, setPageNum] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [totalElementCount, setTotalElementCount] = useState(0) + const [isLastPage, setIsLastPage] = useState(false) + const [isFirstPage, setIsFirstPage] = useState(false) + + const queryRoles = (pageNum: number, pageSize: number, queryRoleForm: QueryRoleForm | null) => { + const queryRoleRequest: QueryRoleRequest = { + code: queryRoleForm?.qCode ?? null, + name: queryRoleForm?.qName ?? null, + status: queryRoleForm?.qStatus ?? null, + pageNum, + pageSize, + } + + RoleApi.fetchRoles(queryRoleRequest) + .then((response) => { + setPageNum(response.pageable.pageNumber + 1) + setPageSize(response.pageable.pageSize) + setTotalElementCount(response.totalElements) + setRoles(response.content.sort((role1, role2) => role1.sort - role2.sort)) + setIsLastPage(response.last) + setIsFirstPage(response.first) + }) + .catch((error) => { + const err = error as AxiosError + void message.error(err.response?.data.message ?? "获取角色失败") + }) + } + + const changeRoleStatus = (roleId: number | string, checked: boolean) => { + const status: Status = checked ? "ACTIVE" : "INACTIVE" + setRoles((prevRoles) => + prevRoles.map((role) => (role.id === roleId ? { ...role, status } : role)) + ) + } + + const onAddRoleFinish = async () => { + try { + const values = await addRoleForm.validateFields() + console.log(values) + await RoleApi.addRole(values) + void message.success(`角色 ${values.name} 创建成功`) + return true + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + // 处理 Axios 请求错误 + const serverMsg = error.response?.data?.message + void message.error(serverMsg ?? "服务异常,请稍后再试") + } else { + // 处理其他预期外的错误(如代码逻辑 Bug) + console.error("未知错误:", error) + void message.error("发生系统错误,请联系管理员") + } + return false + } + } + + const handleAddRole = () => { + modal + .confirm({ + title: "添加用户", + content: , + width: 600, + onOk: onAddRoleFinish, + }) + .then( + () => { + const formValues = queryForm.getFieldsValue() + queryRoles(pageNum, pageSize, formValues) + }, + () => { + console.error("用户取消添加角色") + } + ) + .finally(() => { + addRoleForm.resetFields() + }) + } + + const onEditRoleFinish = async () => { + try { + const values = await editRoleForm.validateFields() + console.log(values) + await RoleApi.editRole(values) + void message.success(`角色 ${values.name} 更新成功`) + return true + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const serverMsg = error.response?.data?.message + void message.error(serverMsg ?? "后端服务异常,请稍后再试") + } else { + console.error("未知错误:", error) + void message.error("发生系统错误,请联系管理员") + } + return false + } + } + + const handleEditRole = (role: Role) => { + modal + .confirm({ + title: "修改用户", + content: , + width: 600, + onOk: onEditRoleFinish, + }) + .then( + () => { + const formValues = queryForm.getFieldsValue() + queryRoles(pageNum, pageSize, formValues) + }, + () => { + console.error("用户取消添加角色") + } + ) + .finally(() => { + editRoleForm.resetFields() + }) + } + + const onDeleteRoleFinish = async (role: Role) => { + try { + await RoleApi.deleteRole(role.id) + void message.info(`角色 ${role.name} 删除成功`) + + // Refresh roles + if (roles.length == 1 && isLastPage && !isFirstPage) { + setPageNum(pageNum - 1) + } else { + // Execute refresh manually + const searchParams = queryForm.getFieldsValue() + queryRoles(pageNum, pageSize, searchParams) + } + + return true + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const serverMsg = error.response?.data?.message + void message.error(serverMsg ?? "服务异常,请稍后再试") + } else { + console.error("未知错误:", error) + void message.error("发生系统错误,请联系管理员") + } + return false + } + } + + const handleDeleteRole = async (role: Role) => { + modal + .confirm({ + title: "删除角色确认", + content: <>删除角色会同步解除该角色与用户的绑定关系,确定要删除吗?, + onOk: () => onDeleteRoleFinish(role), + }) + } + + useEffect(() => { + queryRoles(pageNum, pageSize, null) + }, [pageNum, pageSize]) + + return ( +
+ + form={queryForm} + className="mt-0 mb-6" + layout="inline" + labelAlign="right" + onFinish={(values) => { + queryRoles(pageNum, pageSize, values) + }} + onReset={() => { + queryRoles(pageNum, pageSize, null) + }}> + label="角色名称" name="qName"> + + + label="角色编码" name="qCode"> + + + label="角色状态" name="qStatus"> + + className="w-26" + options={[ + { + label: "已启用", + value: "ACTIVE", + }, + { + label: "未启用", + value: "INACTIVE", + }, + ]} + /> + + > + + + + + + + + + + + + + + + + columns={[ + { title: "角色编号", dataIndex: "id" }, + { title: "角色名称", dataIndex: "name" }, + { title: "角色编码", dataIndex: "code" }, + { title: "显示顺序", dataIndex: "sort" }, + { + title: "状态", + key: "status", + render: (role: Role) => ( + <> + changeRoleStatus(role.id, checked)} + /> + + ), + }, + { + title: "默认角色", + key: "defaultValue", + render: (role: Role) => ( + <> + + + ), + }, + { title: "创建时间", dataIndex: "createdAt" }, + { + title: "操作", + render: (role: Role) => ( + <> + + + + + + ), + key: "operations", + }, + ]} + dataSource={roles} + rowKey="id" + pagination={{ + current: pageNum, + total: totalElementCount, + pageSize: pageSize, + defaultCurrent: 1, + defaultPageSize: 10, + pageSizeOptions: [10, 25, 50], + showSizeChanger: true, + onShowSizeChange: (pageNum, pageSize) => { + console.log(`onSizeChange ==> pageNum = ${pageNum}, pageSize = ${pageSize}`) + setPageNum(pageNum) + setPageSize(pageSize) + }, + onChange: (pageNum, pageSize) => { + console.log(`onChange ==> pageNum = ${pageNum}, pageSize = ${pageSize}`) + setPageNum(pageNum) + setPageSize(pageSize) + }, + }} + /> +
+ ) +} diff --git a/helix-web/src/page/user/index.tsx b/helix-web/src/page/user/index.tsx new file mode 100644 index 0000000..963f7ff --- /dev/null +++ b/helix-web/src/page/user/index.tsx @@ -0,0 +1,463 @@ +import { type Key, useEffect, useState } from "react" +import { + Avatar, + Button, + DatePicker, + Form, + Input, + Select, + Space, + Table, + Tag, + Tree, + type TreeDataNode, + Typography, +} from "antd" +import { App } from "antd" +import { type AxiosError } from "axios" +import { getCountries, getCountryCallingCode } from "libphonenumber-js" +import { DeptApi, UserApi } from "@/api" +import { PhoneNumberUtils, DepartmentUtils } from "@/utils" +import { + DeleteOutlined, + EditOutlined, + ExportOutlined, + ImportOutlined, + KeyOutlined, + PlusOutlined, + SearchOutlined, + UndoOutlined, +} from "@ant-design/icons" +import AddUserDialogue from "@/components/add-user-dialogue" +import EditUserDialogue from "@/components/edit-user-dialogue" +import type { QueryUserRequest } from "@/types/web/request" +import type { QueryUserForm, UserFormValues } from "@/types/form" +import type { GeneralErrorResponse, UserDetailResponse } from "@/types/web/response" +import type { Department } from "@/types/entity" +import type { TreeNode } from "@/types/tree" +import { useAppSelector } from "@/store" + +export default function UserPage() { + const { message, modal } = App.useApp() + + const user = useAppSelector((state) => state.auth.user!) + + const [queryForm] = Form.useForm() + const [addUserForm] = Form.useForm() + const [editUserForm] = Form.useForm() + const [department, setDepartment] = useState>() + const [departmentTree, setDepartmentTree] = useState([]) + const [selectedDepartment, setSelectedDepartment] = useState(1) + const [users, setUsers] = useState([]) + const [pageNum, setPageNum] = useState(1) + const [totalElementCount, setTotalElementCount] = useState(0) + const [pageSize, setPageSize] = useState(10) + + const onAddUserFinish = async () => { + try { + const values = await addUserForm.validateFields() + + await UserApi.addUser(values) + + void message.success(`用户 ${values.username} 创建成功`) + return true + } catch (error: unknown) { + console.log(error) + if (error instanceof Error && error.message.includes("Validation Failed")) { + return false + } + const err = error as AxiosError + void message.error(err.response?.data.message ?? "创建失败,请稍后再试") + return false + } + } + + const handleAddUser = () => { + modal + .confirm({ + title: "添加用户", + content: , + width: 600, + onOk: onAddUserFinish, + }) + .then( + () => { + const formValues = queryForm.getFieldsValue() + queryUsers(pageNum, pageSize, selectedDepartment!, formValues) + }, + () => { + console.error("用户取消添加用户") + } + ) + } + + const onEditUserFinish = async () => { + try { + const values = await editUserForm.validateFields() + + await UserApi.editUser(values) + + void message.success(`用户更新成功`) + return true + } catch (error: unknown) { + if (error instanceof Error && error.message.includes("Validation Failed")) { + return false + } + const err = error as AxiosError + void message.error(err.response?.data.message ?? "更新失败,请稍后再试") + return false + } + } + + const handleEditUser = (user: UserDetailResponse) => { + modal + .confirm({ + title: `编辑用户: ${user.username}`, + content: , + width: 600, + onOk: onEditUserFinish, + }) + .then( + () => { + const formValues = queryForm.getFieldsValue() + queryUsers(pageNum, pageSize, selectedDepartment, formValues) + }, + () => { + console.error("用户取消添加用户") + } + ) + } + + const handleDeleteUser = (user: UserDetailResponse) => { + const isLastElementOnPage = users.length === 1 && pageNum > 1 + modal.confirm({ + title: ( + <> + 确认删除用户 {user.username} 吗? + + ), + content: "删除后数据将无法恢复。", + okText: "删除", + cancelText: "取消", + okButtonProps: { danger: true }, + onOk: async () => { + try { + await UserApi.deleteUser(user.id) + void message.success(`用户 ${user.username} 删除成功`) + setTotalElementCount((count) => count - 1) + if (isLastElementOnPage) { + setPageNum((prevPage) => prevPage - 1) + } else { + queryUsers(pageNum, pageSize, selectedDepartment, queryForm.getFieldsValue()) + } + } catch (error: unknown) { + const err = error as AxiosError + void message.error(err.response?.data.message ?? "删除失败,请稍后再试") + } + }, + }) + } + + // Query users + const queryUsers = ( + pageNum: number, + pageSize: number, + departmentId: number, + formValues: QueryUserForm | null + ) => { + const queryUserRequest: QueryUserRequest = { + createdAtEnd: null, + createdAtStart: null, + phoneNumber: null, + regionAbbreviation: null, + status: null, + username: null, + pageNum, + pageSize, + departmentId, + } + + if (formValues && formValues.qUsername) { + queryUserRequest.username = formValues.qUsername + } + + if (formValues && formValues.qRegionAbbreviation) { + queryUserRequest.regionAbbreviation = formValues.qRegionAbbreviation + } + + if (formValues && formValues.qPhoneNumber) { + queryUserRequest.phoneNumber = formValues.qPhoneNumber + } + + if (formValues && formValues.qStatus) { + queryUserRequest.status = formValues.qStatus + } + + if (formValues && formValues.qCreatedAtStart) { + queryUserRequest.createdAtStart = formValues.qCreatedAtStart.format("YYYY-MM-DD HH:mm:ss") + } + + if (formValues && formValues.qCreatedAtEnd) { + queryUserRequest.createdAtEnd = formValues.qCreatedAtEnd.format("YYYY-MM-DD HH:mm:ss") + } + + console.log(`queryUsers ==> `, queryUserRequest) + + UserApi.fetchUsers(queryUserRequest) + .then((userResponse) => { + console.log(userResponse) + setUsers(userResponse.content) + setPageNum(userResponse.pageable.pageNumber + 1) + setPageSize(userResponse.pageable.pageSize) + setTotalElementCount(userResponse.totalElements) + }) + .catch((error: unknown) => { + const err = error as AxiosError + void message.error(err.response?.data.message ?? "发生未知错误,请稍后再试") + }) + } + + useEffect(() => { + DeptApi.fetchDepartmentTree() + .then((departmentResponse) => { + setDepartment(departmentResponse) + setDepartmentTree(DepartmentUtils.transformDepartmentData([departmentResponse])) + }) + .catch((error: unknown) => { + const err = error as AxiosError + void message.error(err.response?.data.message ?? "发生未知错误,请稍后再试") + }) + }, []) + + useEffect(() => { + const formValues = queryForm.getFieldsValue() + queryUsers(pageNum, pageSize, selectedDepartment, formValues) + }, [pageNum, pageSize, selectedDepartment]) + + const regionOptions = getCountries().map((country) => { + const callingCode = getCountryCallingCode(country) + return { + label: `${country} (+${callingCode})`, + value: country, + key: country, + } + }) + + const userStatusOptions = [ + { + label: "已启用", + value: "ACTIVE", + key: "ACTIVE", + }, + { + label: "已停用", + value: "INACTIVE", + key: "INACTIVE", + }, + { + label: "已禁用", + value: "LOCKED", + key: "LOCKED", + }, + ] + + return ( +
+ { + if (typeof selectedKey == "number") { + setSelectedDepartment(selectedKey) + } + }} + selectedKeys={[selectedDepartment]} + /> +
+ + className="mt-0 mb-6" + form={queryForm} + layout="inline" + labelAlign="right" + onFinish={(values) => { + console.log("Query User Form ==> ", values) + queryUsers(pageNum, pageSize, Number(selectedDepartment), values) + }} + onReset={() => { + queryUsers(pageNum, pageSize, Number(selectedDepartment), null) + }}> + label="用户名" name="qUsername"> + + + + label="用户状态" name="qStatus"> + + + + noStyle name="qPhoneNumber"> + + + + + + + + name="qCreatedAtStart"> + + + name="qCreatedAtEnd"> + + + + + + + + + + + + + + + + + + + + + + rowKey="id" + dataSource={users} + pagination={{ + current: pageNum, + total: totalElementCount, + pageSize: pageSize, + defaultCurrent: 1, + defaultPageSize: 10, + pageSizeOptions: [10, 25, 50], + showSizeChanger: true, + onShowSizeChange: (pageNum, pageSize) => { + console.log(`onSizeChange ==> pageNum = ${pageNum}, pageSize = ${pageSize}`) + setPageNum(pageNum) + setPageSize(pageSize) + }, + onChange: (pageNum, pageSize) => { + console.log(`onChange ==> pageNum = ${pageNum}, pageSize = ${pageSize}`) + setPageNum(pageNum) + setPageSize(pageSize) + }, + }} + columns={[ + { title: "用户 ID", dataIndex: "id" }, + { title: "用户名称", dataIndex: "username" }, + { title: "姓名", dataIndex: "fullName" }, + { title: "电子邮箱", dataIndex: "email" }, + { + title: "联系电话", + render: (user: UserDetailResponse) => { + return PhoneNumberUtils.formatInternationalPhoneNumber( + user.regionAbbreviation, + user.phoneNumber + ) + }, + key: "phoneNumber", + }, + { + title: "头像", + render: (user: UserDetailResponse) => { + return + }, + key: "avatarUrl", + }, + { + title: "用户状态", + render: (user: UserDetailResponse) => { + const colour = user.status == "ACTIVE" ? "green" : "red" + return ( + + {user.status} + + ) + }, + key: "status", + }, + { title: "所在部门", dataIndex: "departmentName" }, + { title: "用户岗位", dataIndex: "positionName" }, + { + title: "操作", + render: (value: UserDetailResponse) => { + return String(value.id) == String(user.id) ? ( + <> + ) : ( + + + + + + ) + }, + }, + ]} + /> +
+
+ ) +} diff --git a/helix-web/src/router/index.tsx b/helix-web/src/router/index.tsx new file mode 100644 index 0000000..5c38b51 --- /dev/null +++ b/helix-web/src/router/index.tsx @@ -0,0 +1,86 @@ +import type { ComponentType } from "react" +import { createBrowserRouter } from "react-router-dom" +import ErrorPage from "@/page/error" +import LoadingFallback from "@/components/loading-fallback" + +/** + * 懒加载组件 + * @param importer + */ +function lazyLoading }>(importer: () => Promise) { + return async () => { + const module = await importer() + return { + Component: module.default, + } + } +} + +const router = createBrowserRouter([ + { + path: "/login", + lazy: lazyLoading(() => import("@/page/login")), + handle: { + label: "用户登录", + }, + hydrateFallbackElement: , + }, + { + path: "/register", + lazy: lazyLoading(() => import("@/page/register")), + handle: { + label: "用户注册", + }, + hydrateFallbackElement: , + }, + { + path: "/", + lazy: lazyLoading(() => import("@/components/protected-route")), + errorElement: , + handle: { + label: "控制台", + }, + hydrateFallbackElement: , + children: [ + { + index: true, + lazy: lazyLoading(() => import("@/page/home")), + handle: { + label: "用户主页", + }, + hydrateFallbackElement: , + }, + { + path: "users", + lazy: lazyLoading(() => import("@/page/user")), + handle: { + label: "用户管理", + }, + hydrateFallbackElement: , + }, + { + path: "roles", + lazy: lazyLoading(() => import("@/page/role")), + handle: { + label: "角色管理", + }, + hydrateFallbackElement: , + }, + { + path: "menus", + lazy: lazyLoading(() => import("@/page/menu")), + handle: { + label: "菜单管理", + }, + hydrateFallbackElement: , + }, + ], + }, + { + path: "*", + lazy: lazyLoading(() => import("@/page/not-found")), + hydrateFallbackElement: , + }, +]) + +export default router diff --git a/helix-web/src/service/auth/index.ts b/helix-web/src/service/auth/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/helix-web/src/service/auth/msal/index.ts b/helix-web/src/service/auth/msal/index.ts new file mode 100644 index 0000000..4eec775 --- /dev/null +++ b/helix-web/src/service/auth/msal/index.ts @@ -0,0 +1,29 @@ +import type { IPublicClientApplication } from "@azure/msal-browser" +import * as AuthApi from "@/api/auth" +import type { AppDispatch } from "@/store" +import { loginSuccess } from "@/store/auth-slice" + +/** + * Login with Microsoft Entra ID. + * + * @param instance Microsoft Entra ID application instance + * @param dispatch app dispatcher + * @param onSuccess callback when login succeeded + */ +export async function doMsalLogin( + instance: IPublicClientApplication, + dispatch: AppDispatch, + onSuccess?: () => void +) { + try { + const response = await instance.loginPopup({ + scopes: ["openid", "profile", "email"], + }) + + const { accessToken, user } = await AuthApi.msalLogin(response.idToken) + dispatch(loginSuccess({ user, token: accessToken })) + if (onSuccess) onSuccess() + } catch (err) { + console.error("MSAL login failed", err) + } +} diff --git a/helix-web/src/store/auth-slice/index.ts b/helix-web/src/store/auth-slice/index.ts new file mode 100644 index 0000000..ee912ad --- /dev/null +++ b/helix-web/src/store/auth-slice/index.ts @@ -0,0 +1,41 @@ +import { createSlice, type PayloadAction } from "@reduxjs/toolkit" +import type { User } from "@/types/entity" + +interface AuthState { + isAuthenticated: boolean + user: User | null + registrationEnabled: boolean +} + +const initialState: AuthState = { + isAuthenticated: false, + user: null, + registrationEnabled: false +} + +const authSlice = createSlice({ + name: "auth", + initialState, + reducers: { + loginSuccess( + state, + action: PayloadAction<{ + user: User + }> + ) { + console.log("更新用户信息:", action.payload.user) + state.isAuthenticated = true + state.user = action.payload.user + }, + logout(state) { + state.isAuthenticated = false + state.user = null + }, + updateRegistrationEnabled(state, action: PayloadAction) { + state.registrationEnabled = action.payload + } + }, +}) + +export const { loginSuccess, logout, updateRegistrationEnabled } = authSlice.actions +export default authSlice.reducer diff --git a/helix-web/src/store/index.ts b/helix-web/src/store/index.ts new file mode 100644 index 0000000..b786fc2 --- /dev/null +++ b/helix-web/src/store/index.ts @@ -0,0 +1,47 @@ +import { configureStore, combineReducers } from "@reduxjs/toolkit" +import { useDispatch, useSelector } from "react-redux" +import authReducer from "./auth-slice" +import { + persistStore, + persistReducer, + FLUSH, + REHYDRATE, + PAUSE, + PERSIST, + PURGE, + REGISTER, +} from "redux-persist" +import storage from "redux-persist/lib/storage/session" + +const persistConfig = { + key: "root", + storage, + whitelist: ["auth"], + // blacklist: ['department'], +} + +const rootReducer = combineReducers({ + auth: authReducer, +}) + +const persistedReducer = persistReducer(persistConfig, rootReducer) + +const store = configureStore({ + reducer: persistedReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: { + ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], + }, + }), +}) + +export const persistor = persistStore(store) + +export default store +export type RootState = ReturnType +export type AppDispatch = typeof store.dispatch +export type AppStore = typeof store + +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector = useSelector.withTypes() diff --git a/helix-web/src/types/antd/index.ts b/helix-web/src/types/antd/index.ts new file mode 100644 index 0000000..42d142e --- /dev/null +++ b/helix-web/src/types/antd/index.ts @@ -0,0 +1,34 @@ +import type { GetProps } from "antd" +import type Icon from "@ant-design/icons" + +export type AntIconComponentProps = GetProps + +export interface AntTreeSelectOption { + value: T + label: string + children: AntTreeSelectOption[] + disabled?: boolean + disableCheckbox?: boolean + selectable?: boolean + checkable?: boolean +} + +export interface AntSelectOptionItem { + label: string + value: string +} + +export type AntSelectOption = AntSelectOptionItem[] + +export interface AntFieldError { + errors: string[] + name: string[] + warnings: unknown[] +} + +export interface AntFormValidationError { + values: T + message: string + outOfDate: boolean + errorFields: AntFieldError[] +} diff --git a/helix-web/src/types/config/index.ts b/helix-web/src/types/config/index.ts new file mode 100644 index 0000000..443140d --- /dev/null +++ b/helix-web/src/types/config/index.ts @@ -0,0 +1,14 @@ +/** + * WeCom config + */ +export type WecomConfig = { + /** + * Corporation ID + */ + corpId: string + + /** + * Application ID + */ + agentId: string +} diff --git a/helix-web/src/types/constant/index.ts b/helix-web/src/types/constant/index.ts new file mode 100644 index 0000000..4083fb8 --- /dev/null +++ b/helix-web/src/types/constant/index.ts @@ -0,0 +1,18 @@ +import type { AntSelectOption } from "@/types/antd" + +/** + * Status + */ +export type Status = "ACTIVE" | "INACTIVE" + +export type UserStatus = Status | "LOCKED" + +export const StatusOptions: AntSelectOption = [ + { label: "已启用", value: "ACTIVE" }, + { label: "已停用", value: "INACTIVE" }, +] + +export const UserStatusOptions: AntSelectOption = [ + ...StatusOptions, + { label: "已禁用", value: "LOCKED" }, +] diff --git a/helix-web/src/types/entity/index.ts b/helix-web/src/types/entity/index.ts new file mode 100644 index 0000000..a9de055 --- /dev/null +++ b/helix-web/src/types/entity/index.ts @@ -0,0 +1,74 @@ +import type { Status, UserStatus } from "@/types/constant" +import type { CountryCode as RegionAbbreviation } from "libphonenumber-js" +import type { Dayjs } from "dayjs" + +/** + * User information + */ +export interface User { + id: number + username: string + password: string + fullName: string + email: string + regionAbbreviation: RegionAbbreviation + phoneNumber: string + avatarUrl: string + status: UserStatus + departmentId: number + positionId: number + createdAt: string + updatedAt: string +} + +/** + * Menu Item + */ +export interface MenuItem { + id: number + name: string + parentId: number | null + code: string + sort: number + path: string | null + isExternalLink: boolean + isVisible: boolean + status: Status + authorityCode: string | null + icon: string | null + createdAt: string + updatedAt: string +} + +export interface Department { + id: number + name: string + parentId: number | null + sort: number + status: Status + createdAt: string + updatedAt: string +} + +export interface Position { + id: number + name: string + code: string | null + description: string | null + sort: number + status: Status + createdAt: string + updatedAt: string +} + +export interface Role { + id: number | string + name: string + code: string + sort: number + defaultValue: boolean + description: string | null + status: Status + createdAt: Dayjs | string + updatedAt: Dayjs | string +} diff --git a/helix-web/src/types/form/index.ts b/helix-web/src/types/form/index.ts new file mode 100644 index 0000000..b26689e --- /dev/null +++ b/helix-web/src/types/form/index.ts @@ -0,0 +1,33 @@ +import type { User } from "@/types/entity" +import type { CountryCode as RegionAbbreviation } from "libphonenumber-js" +import type { Status } from "@/types/constant" +import type { Dayjs } from "dayjs" + +export type FormMode = "add" | "edit" + +export interface UserFormValues extends Omit< + User, + "id" | "password" | "regionAbbreviation" | "departmentId" | "positionId" | "createdAt" | "updatedAt" +> { + id: number | string | null + password: string | null + regionAbbreviation: RegionAbbreviation | null + departmentId: number | null + positionId: number | null +} + +export interface QueryRoleForm { + qName: string | null + qCode: string | null + qStatus: Status | null +} + +export interface QueryUserForm { + qDepartmentId: number | null + qUsername: string | null + qRegionAbbreviation: RegionAbbreviation | null + qPhoneNumber: string | null + qStatus: Status | null + qCreatedAtStart: Dayjs | null + qCreatedAtEnd: Dayjs | null +} diff --git a/helix-web/src/types/index.ts b/helix-web/src/types/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/helix-web/src/types/page/index.ts b/helix-web/src/types/page/index.ts new file mode 100644 index 0000000..9f97449 --- /dev/null +++ b/helix-web/src/types/page/index.ts @@ -0,0 +1,14 @@ +export interface Sortable { + empty: boolean + sorted: boolean + unsorted: boolean +} + +export interface Pageable { + pageNumber: number + pageSize: number + sort: Sortable + offset: number + paged: boolean + unpaged: boolean +} diff --git a/helix-web/src/types/route/index.ts b/helix-web/src/types/route/index.ts new file mode 100644 index 0000000..ae7b549 --- /dev/null +++ b/helix-web/src/types/route/index.ts @@ -0,0 +1,6 @@ +/** + * React Router Metadata + */ +export interface RouteHandle { + label: string +} diff --git a/helix-web/src/types/tree/index.ts b/helix-web/src/types/tree/index.ts new file mode 100644 index 0000000..9495b87 --- /dev/null +++ b/helix-web/src/types/tree/index.ts @@ -0,0 +1,37 @@ +/** + * Represents a single node within a tree structure. + * + * Each node holds a data item of type T and an array of its children. + * + * @template T the type of the data item stored in the node + */ +export interface TreeNode { + /** + * The actual data item contained within this node. + */ + item: T + /** + * A list of child nodes belonging to this node. + * + * This array will be empty if the node is a leaf node. + */ + children: TreeNode[] +} + +/** + * Defines a tree structure, which is simply a top-level tree node. + * + * It represents the root of the hierarchical data structure. + * + * @template T the type of the data items within the tree + */ +export type Tree = TreeNode + +/** + * Defines a forest, which is an array of zero or more disjoint trees. + * + * Each element in the array is the root of an independent tree. + * + * @template T the type of the data items within the trees + */ +export type Forest = TreeNode[] diff --git a/helix-web/src/types/web/request/index.ts b/helix-web/src/types/web/request/index.ts new file mode 100644 index 0000000..93e12be --- /dev/null +++ b/helix-web/src/types/web/request/index.ts @@ -0,0 +1,54 @@ +import type { CountryCode as RegionAbbreviation } from "libphonenumber-js" +import type { Status, UserStatus } from "@/types/constant" +import type { User } from "@/types/entity" + +export interface PageRequest { + pageNum?: number + pageSize?: number +} + +export interface UsernamePasswordLoginRequest { + username: string + password: string + captcha?: string + uuid?: string +} + +export interface QueryUserRequest extends PageRequest { + departmentId: number | null + username: string | null + regionAbbreviation: RegionAbbreviation | null + phoneNumber: string | null + status: Status | null + createdAtStart: string | null + createdAtEnd: string | null +} + +export interface QueryPositionRequest extends PageRequest { +} + +export interface AddUserRequest extends Omit{ + roleIds: number[] | null +} + +export interface EditUserRequest { + id: number | string + username: string | null + fullName: string | null + email: string | null + regionAbbreviation: string | null + phoneNumber: string | null + avatarUrl: string | null + status: UserStatus | null + departmentId: number | null + positionId: number | null + roleIds: number[] | null +} + +export interface QueryRoleRequest extends PageRequest { + name: string | null + code: string | null + status: Status | null +} + + diff --git a/helix-web/src/types/web/response/index.ts b/helix-web/src/types/web/response/index.ts new file mode 100644 index 0000000..6081d11 --- /dev/null +++ b/helix-web/src/types/web/response/index.ts @@ -0,0 +1,41 @@ +import type { Role, User } from "@/types/entity" +import type { Pageable, Sortable } from "@/types/page" + +export interface PageResponse { + content: T[] + last: boolean + totalPages: number + totalElements: number + size: number + sort: Sortable + first: boolean + numberOfElements: number + empty: boolean, + pageable: Pageable +} + +export interface UserAuthResponse extends User { +} + +export interface MfaAuthResponse { + mfaToken: string +} + +export interface UserDetailResponse extends User { + departmentName: string + positionName: string +} + +export interface CaptchaResponse { + uuid: string + captcha: string +} + +export interface GeneralErrorResponse { + message: string + timestamp: string +} + +export interface RoleResponse extends PageResponse { +} + diff --git a/helix-web/src/utils/ant-utils/index.ts b/helix-web/src/utils/ant-utils/index.ts new file mode 100644 index 0000000..69dd794 --- /dev/null +++ b/helix-web/src/utils/ant-utils/index.ts @@ -0,0 +1,5 @@ +import type { AntFormValidationError } from "@/types/antd" + +export function getFieldErrorMessage(error: AntFormValidationError): string { + return error.errorFields.map((errorField) => errorField.errors.join(",")).join(";") +} diff --git a/helix-web/src/utils/app-utils/index.ts b/helix-web/src/utils/app-utils/index.ts new file mode 100644 index 0000000..8a4f302 --- /dev/null +++ b/helix-web/src/utils/app-utils/index.ts @@ -0,0 +1,6 @@ +/** + * Get application title. + */ +export function getAppTitle(): string { + return import.meta.env.VITE_APP_TITLE ?? "Helix" +} diff --git a/helix-web/src/utils/department-utils/index.ts b/helix-web/src/utils/department-utils/index.ts new file mode 100644 index 0000000..76481f3 --- /dev/null +++ b/helix-web/src/utils/department-utils/index.ts @@ -0,0 +1,28 @@ +import { type TreeDataNode } from "antd" +import type { TreeNode } from "@/types/tree" +import type { Department } from "@/types/entity" + +export function transformDepartmentData(departments: TreeNode[]): TreeDataNode[] { + if (!departments || departments.length === 0) { + return [] + } + + return departments + .sort((a, b) => a.item.sort - b.item.sort) + .map((node) => { + const { item, children } = node + const hasChildren = children && children.length > 0 + + const treeItem: TreeDataNode = { + key: item.id, + title: item.name, + } + + if (hasChildren) { + // Append children + return { ...treeItem, children: transformDepartmentData(children) } + } + + return treeItem + }) +} diff --git a/helix-web/src/utils/index.ts b/helix-web/src/utils/index.ts new file mode 100644 index 0000000..106472a --- /dev/null +++ b/helix-web/src/utils/index.ts @@ -0,0 +1,4 @@ +export * as PhoneNumberUtils from "./phone-number-utils" +export * as DepartmentUtils from "./department-utils" +export * as AntUtils from "./ant-utils" +export * as AppUtils from "./app-utils" diff --git a/helix-web/src/utils/phone-number-utils/index.ts b/helix-web/src/utils/phone-number-utils/index.ts new file mode 100644 index 0000000..0196a5b --- /dev/null +++ b/helix-web/src/utils/phone-number-utils/index.ts @@ -0,0 +1,28 @@ +import { type CountryCode as RegionAbbreviation, getCountryCallingCode, parsePhoneNumberWithError, PhoneNumber } from "libphonenumber-js" + +/** + * Format user's phone number as user's country/region. + * + * @param regionAbbreviation user's region abbreviation + * @param phoneNumber user's phone number + * @return formatted phone number + */ +export function formatInternationalPhoneNumber(regionAbbreviation: string, phoneNumber: string): string { + try { + const _phoneNumber = parsePhoneNumberWithError(phoneNumber, regionAbbreviation as RegionAbbreviation) + + if (!_phoneNumber.isValid()) { + console.warn(`Phone number ${_phoneNumber.formatInternational()} is not valid`) + } + return _phoneNumber.formatInternational() + } catch (error) { + console.error("Phone number parsing failed:", error) + const _regionAbbreviation: RegionAbbreviation = regionAbbreviation as RegionAbbreviation + const callingCode = getCountryCallingCode(_regionAbbreviation) + return `+${callingCode} ${phoneNumber}` + } +} + +export function getDefaultCountryCode(): RegionAbbreviation { + return import.meta.env.VITE_DEFAULT_REGION_CODE +} diff --git a/helix-web/src/vite-env.d.ts b/helix-web/src/vite-env.d.ts new file mode 100644 index 0000000..8c4ddc0 --- /dev/null +++ b/helix-web/src/vite-env.d.ts @@ -0,0 +1,35 @@ +/// +/// + +import type { CountryCode as RegionAbbreviation } from "libphonenumber-js" + +interface ImportMetaEnv { + /** + * Server Base URL + */ + readonly VITE_API_BASE_URL: string + + /** + * Microsoft Entra ID Client ID + */ + readonly VITE_MSAL_CLIENT_ID: string + + /** + * Microsoft Entra ID Tenant ID + */ + readonly VITE_MSAL_TENANT_ID: string + + /** + * Default region for new users + */ + readonly VITE_DEFAULT_REGION_ABBREVIATION: RegionAbbreviation + + /** + * Application title, can be set to any preferred string as your wish. + */ + readonly VITE_APP_TITLE: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/helix-web/tsconfig.app.json b/helix-web/tsconfig.app.json new file mode 100644 index 0000000..9b30999 --- /dev/null +++ b/helix-web/tsconfig.app.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "baseUrl": ".", + "paths": { + "@/*": [ + "./src/*" + ] + } + }, + "include": ["src"] +} diff --git a/helix-web/tsconfig.json b/helix-web/tsconfig.json new file mode 100644 index 0000000..fec8c8e --- /dev/null +++ b/helix-web/tsconfig.json @@ -0,0 +1,13 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/helix-web/tsconfig.node.json b/helix-web/tsconfig.node.json new file mode 100644 index 0000000..70d82d6 --- /dev/null +++ b/helix-web/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/helix-web/vite.config.ts b/helix-web/vite.config.ts new file mode 100644 index 0000000..a5708a3 --- /dev/null +++ b/helix-web/vite.config.ts @@ -0,0 +1,18 @@ +import { fileURLToPath, URL } from "node:url" +import { defineConfig } from "vite" +import react from "@vitejs/plugin-react" +import tailwindcss from "@tailwindcss/vite" +import svgr from "vite-plugin-svgr" + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss(), svgr()], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, + server: { + allowedHosts: true, + } +})