diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 51256ca..0000000
--- a/.eslintignore
+++ /dev/null
@@ -1,6 +0,0 @@
-dist
-node_modules
-*.config.js
-*.config.ts
-.github
-public
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
deleted file mode 100644
index 5df859f..0000000
--- a/.eslintrc.cjs
+++ /dev/null
@@ -1,22 +0,0 @@
-module.exports = {
- root: true,
- env: { browser: true, es2020: true },
- extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:react-hooks/recommended',
- ],
- ignorePatterns: ['dist', '.eslintrc.cjs'],
- parser: '@typescript-eslint/parser',
- plugins: ['react-refresh'],
- rules: {
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
- '@typescript-eslint/no-explicit-any': 'warn',
- '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
- 'react-hooks/exhaustive-deps': 'warn',
- 'no-empty': 'warn',
- },
-}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8414a20..5630838 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,3 +1,5 @@
+name: CI
+
on:
push:
branches: [main, dev]
@@ -5,7 +7,11 @@ on:
branches: [main, dev]
jobs:
- lint-and-build:
+ lint:
+ uses: ./.github/workflows/lint.yml
+
+ build:
+ needs: lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -20,12 +26,6 @@ jobs:
- name: Install dependencies
run: npm ci
- - name: Run ESLint
- run: npm run lint
-
- - name: Run TypeScript check
- run: npx tsc --noEmit
-
- name: Build
run: npm run build
env:
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 57fbd02..10df084 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -13,7 +13,11 @@ env:
IMAGE_NAME: ${{ github.repository }}
jobs:
+ lint:
+ uses: ./.github/workflows/lint.yml
+
build-and-push:
+ needs: lint
runs-on: ubuntu-latest
permissions:
contents: read
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..e942159
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,29 @@
+name: Lint & Format
+
+on:
+ workflow_call:
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run ESLint
+ run: npm run lint
+
+ - name: Check formatting
+ run: npm run format:check
+
+ - name: Run TypeScript check
+ run: npx tsc --noEmit
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fe4ab04..32ccc19 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,7 +9,11 @@ permissions:
contents: write
jobs:
+ lint:
+ uses: ./.github/workflows/lint.yml
+
create-release:
+ needs: lint
runs-on: ubuntu-latest
steps:
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..2312dc5
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+npx lint-staged
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..53a044f
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+dist
+node_modules
+public
+*.min.js
+*.min.css
+package-lock.json
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..ca25bd0
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,13 @@
+{
+ "semi": true,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "useTabs": false,
+ "trailingComma": "all",
+ "printWidth": 100,
+ "bracketSpacing": true,
+ "arrowParens": "always",
+ "endOfLine": "auto",
+ "jsxSingleQuote": false,
+ "plugins": ["prettier-plugin-tailwindcss"]
+}
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..f33a89d
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,48 @@
+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 eslintConfigPrettier from 'eslint-config-prettier'
+
+export default tseslint.config(
+ {
+ ignores: ['dist', 'node_modules', 'public'],
+ },
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ ...tseslint.configs.recommended,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-hooks/set-state-in-effect': 'off',
+ 'react-hooks/static-components': 'off',
+ 'react-hooks/immutability': 'off',
+ 'react-hooks/refs': 'off',
+ 'react-hooks/purity': 'off',
+ 'react-hooks/variables': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ '@typescript-eslint/no-explicit-any': 'warn',
+ '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
+ 'no-empty': 'warn',
+ 'no-eval': 'error',
+ 'no-implied-eval': 'error',
+ 'no-new-func': 'error',
+ 'no-script-url': 'error',
+ },
+ },
+ eslintConfigPrettier,
+)
diff --git a/package-lock.json b/package-lock.json
index c2b6cba..6b76818 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,24 +20,30 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^13.5.0",
- "react-router-dom": "^6.20.0",
+ "react-router-dom": "^7.13.0",
"zustand": "^4.4.0"
},
"devDependencies": {
+ "@eslint/js": "^9.39.2",
"@types/dompurify": "^3.0.5",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
- "@typescript-eslint/eslint-plugin": "^6.10.0",
- "@typescript-eslint/parser": "^6.10.0",
- "@vitejs/plugin-react": "^4.2.0",
+ "@vitejs/plugin-react": "^5.1.2",
"autoprefixer": "^10.4.16",
- "eslint": "^8.53.0",
- "eslint-plugin-react-hooks": "^4.6.0",
- "eslint-plugin-react-refresh": "^0.4.4",
+ "eslint": "^9.39.2",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.26",
+ "globals": "^17.1.0",
+ "husky": "^9.1.7",
+ "lint-staged": "^16.2.7",
"postcss": "^8.4.31",
+ "prettier": "^3.8.1",
+ "prettier-plugin-tailwindcss": "^0.7.2",
"tailwindcss": "^3.3.5",
"typescript": "^5.2.2",
- "vite": "^5.0.0"
+ "typescript-eslint": "^8.54.0",
+ "vite": "^7.3.1"
}
},
"node_modules/@alloc/quick-lru": {
@@ -420,9 +426,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
"cpu": [
"ppc64"
],
@@ -433,13 +439,13 @@
"aix"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
"cpu": [
"arm"
],
@@ -450,13 +456,13 @@
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
"cpu": [
"arm64"
],
@@ -467,13 +473,13 @@
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
"cpu": [
"x64"
],
@@ -484,13 +490,13 @@
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
"cpu": [
"arm64"
],
@@ -501,13 +507,13 @@
"darwin"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
"cpu": [
"x64"
],
@@ -518,13 +524,13 @@
"darwin"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
"cpu": [
"arm64"
],
@@ -535,13 +541,13 @@
"freebsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
"cpu": [
"x64"
],
@@ -552,13 +558,13 @@
"freebsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
"cpu": [
"arm"
],
@@ -569,13 +575,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
"cpu": [
"arm64"
],
@@ -586,13 +592,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
"cpu": [
"ia32"
],
@@ -603,13 +609,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
"cpu": [
"loong64"
],
@@ -620,13 +626,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
"cpu": [
"mips64el"
],
@@ -637,13 +643,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
"cpu": [
"ppc64"
],
@@ -654,13 +660,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
"cpu": [
"riscv64"
],
@@ -671,13 +677,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
"cpu": [
"s390x"
],
@@ -688,13 +694,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
"cpu": [
"x64"
],
@@ -705,13 +711,30 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
"cpu": [
"x64"
],
@@ -722,13 +745,30 @@
"netbsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
"cpu": [
"x64"
],
@@ -739,13 +779,30 @@
"openbsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
"cpu": [
"x64"
],
@@ -756,13 +813,13 @@
"sunos"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
"cpu": [
"arm64"
],
@@ -773,13 +830,13 @@
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
"cpu": [
"ia32"
],
@@ -790,13 +847,13 @@
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
"cpu": [
"x64"
],
@@ -807,7 +864,7 @@
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": {
@@ -829,6 +886,19 @@
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/@eslint-community/regexpp": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
@@ -839,102 +909,143 @@
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
"node_modules/@eslint/eslintrc": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
- "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.6.0",
- "globals": "^13.19.0",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
+ "js-yaml": "^4.1.1",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
"engines": {
- "node": "*"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/js": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
- "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
}
},
- "node_modules/@humanwhocodes/config-array": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
- "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
- "deprecated": "Use @eslint/config-array instead",
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@humanwhocodes/object-schema": "^2.0.3",
- "debug": "^4.3.1",
- "minimatch": "^3.0.5"
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
},
"engines": {
- "node": ">=10.10.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
- "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
}
},
- "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"dev": true,
- "license": "ISC",
+ "license": "Apache-2.0",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
},
"engines": {
- "node": "*"
+ "node": ">=18.18.0"
}
},
"node_modules/@humanwhocodes/module-importer": {
@@ -951,13 +1062,19 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
- "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
- "deprecated": "Use @eslint/object-schema instead",
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
@@ -1066,19 +1183,10 @@
"node": ">= 8"
}
},
- "node_modules/@remix-run/router": {
- "version": "1.23.1",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz",
- "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==",
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-beta.27",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
- "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "version": "1.0.0-beta.53",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
+ "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
"dev": true,
"license": "MIT"
},
@@ -1514,13 +1622,6 @@
"@types/react": "^18.0.0"
}
},
- "node_modules/@types/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
@@ -1529,125 +1630,160 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
- "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
+ "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "6.21.0",
- "@typescript-eslint/type-utils": "6.21.0",
- "@typescript-eslint/utils": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0",
- "debug": "^4.3.4",
- "graphemer": "^1.4.0",
- "ignore": "^5.2.4",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.54.0",
+ "@typescript-eslint/type-utils": "8.54.0",
+ "@typescript-eslint/utils": "8.54.0",
+ "@typescript-eslint/visitor-keys": "8.54.0",
+ "ignore": "^7.0.5",
"natural-compare": "^1.4.0",
- "semver": "^7.5.4",
- "ts-api-utils": "^1.0.1"
+ "ts-api-utils": "^2.4.0"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
- "eslint": "^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "@typescript-eslint/parser": "^8.54.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
- "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
+ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
"peer": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "6.21.0",
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/typescript-estree": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0",
- "debug": "^4.3.4"
+ "@typescript-eslint/scope-manager": "8.54.0",
+ "@typescript-eslint/types": "8.54.0",
+ "@typescript-eslint/typescript-estree": "8.54.0",
+ "@typescript-eslint/visitor-keys": "8.54.0",
+ "debug": "^4.4.3"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
+ "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.54.0",
+ "@typescript-eslint/types": "^8.54.0",
+ "debug": "^4.4.3"
},
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
- "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
+ "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0"
+ "@typescript-eslint/types": "8.54.0",
+ "@typescript-eslint/visitor-keys": "8.54.0"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@typescript-eslint/type-utils": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
- "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
+ "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@typescript-eslint/typescript-estree": "6.21.0",
- "@typescript-eslint/utils": "6.21.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^1.0.1"
- },
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
+ "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.54.0",
+ "@typescript-eslint/typescript-estree": "8.54.0",
+ "@typescript-eslint/utils": "8.54.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
},
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
- "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
+ "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
@@ -1655,101 +1791,117 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
- "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0",
- "debug": "^4.3.4",
- "globby": "^11.1.0",
- "is-glob": "^4.0.3",
- "minimatch": "9.0.3",
- "semver": "^7.5.4",
- "ts-api-utils": "^1.0.1"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
- "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
+ "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@types/json-schema": "^7.0.12",
- "@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "6.21.0",
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/typescript-estree": "6.21.0",
- "semver": "^7.5.4"
+ "@typescript-eslint/project-service": "8.54.0",
+ "@typescript-eslint/tsconfig-utils": "8.54.0",
+ "@typescript-eslint/types": "8.54.0",
+ "@typescript-eslint/visitor-keys": "8.54.0",
+ "debug": "^4.4.3",
+ "minimatch": "^9.0.5",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
- "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "6.21.0",
- "eslint-visitor-keys": "^3.4.1"
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
+ "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.54.0",
+ "@typescript-eslint/types": "8.54.0",
+ "@typescript-eslint/typescript-estree": "8.54.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
+ "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.54.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@ungap/structured-clone": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
- "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/@vitejs/plugin-react": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
- "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz",
+ "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/core": "^7.28.0",
+ "@babel/core": "^7.28.5",
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@rolldown/pluginutils": "1.0.0-beta.53",
"@types/babel__core": "^7.20.5",
- "react-refresh": "^0.17.0"
+ "react-refresh": "^0.18.0"
},
"engines": {
- "node": "^14.18.0 || >=16.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
@@ -1796,14 +1948,33 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
+ "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "environment": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
@@ -1857,16 +2028,6 @@
"dev": true,
"license": "Python-2.0"
},
- "node_modules/array-union": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
- "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -1952,13 +2113,14 @@
}
},
"node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
}
},
"node_modules/braces": {
@@ -2118,6 +2280,39 @@
"node": ">= 6"
}
},
+ "node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
+ "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "slice-ansi": "^7.1.0",
+ "string-width": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2138,6 +2333,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -2174,6 +2376,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2250,19 +2465,6 @@
"dev": true,
"license": "Apache-2.0"
},
- "node_modules/dir-glob": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
- "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-type": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -2270,19 +2472,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/doctrine": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
- "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/dompurify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
@@ -2313,6 +2502,26 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -2359,9 +2568,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -2369,32 +2578,35 @@
"esbuild": "bin/esbuild"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@esbuild/win32-x64": "0.21.5"
+ "@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"
}
},
"node_modules/escalade": {
@@ -2421,74 +2633,100 @@
}
},
"node_modules/eslint": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
- "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
- "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.6.1",
- "@eslint/eslintrc": "^2.1.4",
- "@eslint/js": "8.57.1",
- "@humanwhocodes/config-array": "^0.13.0",
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.2",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
- "@nodelib/fs.walk": "^1.2.8",
- "@ungap/structured-clone": "^1.2.0",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
- "cross-spawn": "^7.0.2",
+ "cross-spawn": "^7.0.6",
"debug": "^4.3.2",
- "doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.2",
- "eslint-visitor-keys": "^3.4.3",
- "espree": "^9.6.1",
- "esquery": "^1.4.2",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^6.0.1",
+ "file-entry-cache": "^8.0.0",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
- "globals": "^13.19.0",
- "graphemer": "^1.4.0",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
- "is-path-inside": "^3.0.3",
- "js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.3",
- "strip-ansi": "^6.0.1",
- "text-table": "^0.2.0"
+ "optionator": "^0.9.3"
},
"bin": {
"eslint": "bin/eslint.js"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://opencollective.com/eslint"
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-react-hooks": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
- "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
}
},
"node_modules/eslint-plugin-react-refresh": {
@@ -2502,9 +2740,9 @@
}
},
"node_modules/eslint-scope": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
- "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -2512,62 +2750,38 @@
"estraverse": "^5.2.0"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/espree": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
- "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "acorn": "^8.9.0",
+ "acorn": "^8.15.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.4.1"
+ "eslint-visitor-keys": "^4.2.1"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -2619,6 +2833,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eventemitter3": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
+ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2681,16 +2902,16 @@
}
},
"node_modules/file-entry-cache": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
- "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "flat-cache": "^3.0.4"
+ "flat-cache": "^4.0.0"
},
"engines": {
- "node": "^10.12.0 || >=12.0.0"
+ "node": ">=16.0.0"
}
},
"node_modules/fill-range": {
@@ -2724,18 +2945,17 @@
}
},
"node_modules/flat-cache": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
- "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"flatted": "^3.2.9",
- "keyv": "^4.5.3",
- "rimraf": "^3.0.2"
+ "keyv": "^4.5.4"
},
"engines": {
- "node": "^10.12.0 || >=12.0.0"
+ "node": ">=16"
}
},
"node_modules/flatted": {
@@ -2795,13 +3015,6 @@
"url": "https://github.com/sponsors/rawify"
}
},
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2836,6 +3049,19 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-east-asian-width": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -2873,28 +3099,6 @@
"node": ">= 0.4"
}
},
- "node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2908,62 +3112,14 @@
"node": ">=10.13.0"
}
},
- "node_modules/glob/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/glob/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "version": "17.1.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz",
+ "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "type-fest": "^0.20.2"
- },
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/globby": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
- "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-union": "^2.1.0",
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.9",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -2981,13 +3137,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3037,6 +3186,23 @@
"node": ">= 0.4"
}
},
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
@@ -3046,6 +3212,22 @@
"void-elements": "3.1.0"
}
},
+ "node_modules/husky": {
+ "version": "9.1.7",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
+ "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "husky": "bin.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
"node_modules/i18next": {
"version": "23.16.8",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz",
@@ -3116,25 +3298,6 @@
"node": ">=0.8.19"
}
},
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -3174,6 +3337,22 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -3197,16 +3376,6 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-path-inside": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3335,6 +3504,59 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lint-staged": {
+ "version": "16.2.7",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz",
+ "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^14.0.2",
+ "listr2": "^9.0.5",
+ "micromatch": "^4.0.8",
+ "nano-spawn": "^2.0.0",
+ "pidtree": "^0.6.0",
+ "string-argv": "^0.3.2",
+ "yaml": "^2.8.1"
+ },
+ "bin": {
+ "lint-staged": "bin/lint-staged.js"
+ },
+ "engines": {
+ "node": ">=20.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/lint-staged"
+ }
+ },
+ "node_modules/lint-staged/node_modules/commander": {
+ "version": "14.0.2",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
+ "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/listr2": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz",
+ "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cli-truncate": "^5.0.0",
+ "colorette": "^2.0.20",
+ "eventemitter3": "^5.0.1",
+ "log-update": "^6.1.0",
+ "rfdc": "^1.4.1",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -3358,6 +3580,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/log-update": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^7.0.0",
+ "cli-cursor": "^5.0.0",
+ "slice-ansi": "^7.1.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -3434,20 +3676,30 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
- "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "brace-expansion": "^1.1.7"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": "*"
}
},
"node_modules/ms": {
@@ -3469,6 +3721,19 @@
"thenify-all": "^1.0.0"
}
},
+ "node_modules/nano-spawn": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
+ "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -3532,14 +3797,20 @@
"node": ">= 6"
}
},
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "wrappy": "1"
+ "mimic-function": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/optionator": {
@@ -3615,16 +3886,6 @@
"node": ">=8"
}
},
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -3642,16 +3903,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/path-type": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
- "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -3672,6 +3923,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pidtree": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
+ "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "pidtree": "bin/pidtree.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -3866,6 +4130,102 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
+ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz",
+ "integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-hermes": "*",
+ "@prettier/plugin-oxc": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-multiline-arrays": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-hermes": {
+ "optional": true
+ },
+ "@prettier/plugin-oxc": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-multiline-arrays": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -3953,9 +4313,9 @@
}
},
"node_modules/react-refresh": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
- "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3963,35 +4323,41 @@
}
},
"node_modules/react-router": {
- "version": "6.30.2",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz",
- "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
+ "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.1"
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=20.0.0"
},
"peerDependencies": {
- "react": ">=16.8"
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
}
},
"node_modules/react-router-dom": {
- "version": "6.30.2",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz",
- "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
+ "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.1",
- "react-router": "6.30.2"
+ "react-router": "7.13.0"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=20.0.0"
},
"peerDependencies": {
- "react": ">=16.8",
- "react-dom": ">=16.8"
+ "react": ">=18",
+ "react-dom": ">=18"
}
},
"node_modules/read-cache": {
@@ -4048,6 +4414,23 @@
"node": ">=4"
}
},
+ "node_modules/restore-cursor": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -4059,22 +4442,12 @@
"node": ">=0.10.0"
}
},
- "node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
+ "license": "MIT"
},
"node_modules/rollup": {
"version": "4.54.0",
@@ -4164,6 +4537,12 @@
"node": ">=10"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -4187,14 +4566,47 @@
"node": ">=8"
}
},
- "node_modules/slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
+ "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/source-map-js": {
@@ -4207,17 +4619,47 @@
"node": ">=0.10.0"
}
},
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "node_modules/string-argv": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
+ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.19"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
+ "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "get-east-asian-width": "^1.3.0",
+ "strip-ansi": "^7.1.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-json-comments": {
@@ -4320,13 +4762,6 @@
"node": ">=14.0.0"
}
},
- "node_modules/text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -4413,16 +4848,16 @@
}
},
"node_modules/ts-api-utils": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
- "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=16"
+ "node": ">=18.12"
},
"peerDependencies": {
- "typescript": ">=4.2.0"
+ "typescript": ">=4.8.4"
}
},
"node_modules/ts-interface-checker": {
@@ -4451,19 +4886,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -4479,6 +4901,30 @@
"node": ">=14.17"
}
},
+ "node_modules/typescript-eslint": {
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz",
+ "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.54.0",
+ "@typescript-eslint/parser": "8.54.0",
+ "@typescript-eslint/typescript-estree": "8.54.0",
+ "@typescript-eslint/utils": "8.54.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -4537,22 +4983,25 @@
"license": "MIT"
},
"node_modules/vite": {
- "version": "5.4.21",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
- "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "esbuild": "^0.21.3",
- "postcss": "^8.4.43",
- "rollup": "^4.20.0"
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -4561,19 +5010,25 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
- "less": "*",
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
"lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.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
},
@@ -4594,9 +5049,47 @@
},
"terser": {
"optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
}
}
},
+ "node_modules/vite/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
@@ -4632,12 +5125,54 @@
"node": ">=0.10.0"
}
},
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"dev": true,
- "license": "ISC"
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
},
"node_modules/yallist": {
"version": "3.1.1",
@@ -4646,6 +5181,22 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/yaml": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
+ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -4659,6 +5210,30 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ },
"node_modules/zustand": {
"version": "4.5.7",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
diff --git a/package.json b/package.json
index 3d4670a..558b344 100644
--- a/package.json
+++ b/package.json
@@ -2,13 +2,19 @@
"name": "cabinet-frontend",
"private": true,
"version": "1.0.0",
+ "description": "Remnawave Bedolaga Web App",
+ "license": "AGPL-3.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
- "lint": "eslint . --ext ts,tsx --report-unused-disable-directives",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"",
+ "format:check": "prettier --check \"src/**/*.{ts,tsx,css,json}\"",
"type-check": "tsc --noEmit",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "prepare": "husky"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@@ -23,23 +29,38 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^13.5.0",
- "react-router-dom": "^6.20.0",
+ "react-router-dom": "^7.13.0",
"zustand": "^4.4.0"
},
"devDependencies": {
+ "@eslint/js": "^9.39.2",
"@types/dompurify": "^3.0.5",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
- "@typescript-eslint/eslint-plugin": "^6.10.0",
- "@typescript-eslint/parser": "^6.10.0",
- "@vitejs/plugin-react": "^4.2.0",
+ "@vitejs/plugin-react": "^5.1.2",
"autoprefixer": "^10.4.16",
- "eslint": "^8.53.0",
- "eslint-plugin-react-hooks": "^4.6.0",
- "eslint-plugin-react-refresh": "^0.4.4",
+ "eslint": "^9.39.2",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.26",
+ "globals": "^17.1.0",
+ "husky": "^9.1.7",
+ "lint-staged": "^16.2.7",
"postcss": "^8.4.31",
+ "prettier": "^3.8.1",
+ "prettier-plugin-tailwindcss": "^0.7.2",
"tailwindcss": "^3.3.5",
"typescript": "^5.2.2",
- "vite": "^5.0.0"
+ "typescript-eslint": "^8.54.0",
+ "vite": "^7.3.1"
+ },
+ "lint-staged": {
+ "*.{ts,tsx}": [
+ "eslint --fix",
+ "prettier --write"
+ ],
+ "*.{css,json}": [
+ "prettier --write"
+ ]
}
}
diff --git a/src/App.tsx b/src/App.tsx
index be685c4..23cc45b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,364 +1,416 @@
-import { lazy, Suspense } from 'react'
-import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
-import { useAuthStore } from './store/auth'
-import { useBlockingStore } from './store/blocking'
-import Layout from './components/layout/Layout'
-import PageLoader from './components/common/PageLoader'
-import { MaintenanceScreen, ChannelSubscriptionScreen } from './components/blocking'
-import { saveReturnUrl } from './utils/token'
-import { useAnalyticsCounters } from './hooks/useAnalyticsCounters'
+import { lazy, Suspense } from 'react';
+import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
+import { useAuthStore } from './store/auth';
+import { useBlockingStore } from './store/blocking';
+import Layout from './components/layout/Layout';
+import PageLoader from './components/common/PageLoader';
+import { MaintenanceScreen, ChannelSubscriptionScreen } from './components/blocking';
+import { saveReturnUrl } from './utils/token';
+import { useAnalyticsCounters } from './hooks/useAnalyticsCounters';
// Auth pages - load immediately (small)
-import Login from './pages/Login'
-import TelegramCallback from './pages/TelegramCallback'
-import TelegramRedirect from './pages/TelegramRedirect'
-import DeepLinkRedirect from './pages/DeepLinkRedirect'
-import VerifyEmail from './pages/VerifyEmail'
-import ResetPassword from './pages/ResetPassword'
+import Login from './pages/Login';
+import TelegramCallback from './pages/TelegramCallback';
+import TelegramRedirect from './pages/TelegramRedirect';
+import DeepLinkRedirect from './pages/DeepLinkRedirect';
+import VerifyEmail from './pages/VerifyEmail';
+import ResetPassword from './pages/ResetPassword';
// User pages - lazy load
-const Dashboard = lazy(() => import('./pages/Dashboard'))
-const Subscription = lazy(() => import('./pages/Subscription'))
-const Balance = lazy(() => import('./pages/Balance'))
-const Referral = lazy(() => import('./pages/Referral'))
-const Support = lazy(() => import('./pages/Support'))
-const Profile = lazy(() => import('./pages/Profile'))
-const Contests = lazy(() => import('./pages/Contests'))
-const Polls = lazy(() => import('./pages/Polls'))
-const Info = lazy(() => import('./pages/Info'))
-const Wheel = lazy(() => import('./pages/Wheel'))
+const Dashboard = lazy(() => import('./pages/Dashboard'));
+const Subscription = lazy(() => import('./pages/Subscription'));
+const Balance = lazy(() => import('./pages/Balance'));
+const Referral = lazy(() => import('./pages/Referral'));
+const Support = lazy(() => import('./pages/Support'));
+const Profile = lazy(() => import('./pages/Profile'));
+const Contests = lazy(() => import('./pages/Contests'));
+const Polls = lazy(() => import('./pages/Polls'));
+const Info = lazy(() => import('./pages/Info'));
+const Wheel = lazy(() => import('./pages/Wheel'));
// Admin pages - lazy load (only for admins)
-const AdminPanel = lazy(() => import('./pages/AdminPanel'))
-const AdminTickets = lazy(() => import('./pages/AdminTickets'))
-const AdminSettings = lazy(() => import('./pages/AdminSettings'))
-const AdminApps = lazy(() => import('./pages/AdminApps'))
-const AdminWheel = lazy(() => import('./pages/AdminWheel'))
-const AdminTariffs = lazy(() => import('./pages/AdminTariffs'))
-const AdminServers = lazy(() => import('./pages/AdminServers'))
-const AdminDashboard = lazy(() => import('./pages/AdminDashboard'))
-const AdminBanSystem = lazy(() => import('./pages/AdminBanSystem'))
-const AdminBroadcasts = lazy(() => import('./pages/AdminBroadcasts'))
-const AdminPromocodes = lazy(() => import('./pages/AdminPromocodes'))
-const AdminCampaigns = lazy(() => import('./pages/AdminCampaigns'))
-const AdminUsers = lazy(() => import('./pages/AdminUsers'))
-const AdminPayments = lazy(() => import('./pages/AdminPayments'))
-const AdminPaymentMethods = lazy(() => import('./pages/AdminPaymentMethods'))
-const AdminPromoOffers = lazy(() => import('./pages/AdminPromoOffers'))
-const AdminRemnawave = lazy(() => import('./pages/AdminRemnawave'))
-const AdminEmailTemplates = lazy(() => import('./pages/AdminEmailTemplates'))
+const AdminPanel = lazy(() => import('./pages/AdminPanel'));
+const AdminTickets = lazy(() => import('./pages/AdminTickets'));
+const AdminSettings = lazy(() => import('./pages/AdminSettings'));
+const AdminApps = lazy(() => import('./pages/AdminApps'));
+const AdminWheel = lazy(() => import('./pages/AdminWheel'));
+const AdminTariffs = lazy(() => import('./pages/AdminTariffs'));
+const AdminServers = lazy(() => import('./pages/AdminServers'));
+const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
+const AdminBanSystem = lazy(() => import('./pages/AdminBanSystem'));
+const AdminBroadcasts = lazy(() => import('./pages/AdminBroadcasts'));
+const AdminPromocodes = lazy(() => import('./pages/AdminPromocodes'));
+const AdminCampaigns = lazy(() => import('./pages/AdminCampaigns'));
+const AdminUsers = lazy(() => import('./pages/AdminUsers'));
+const AdminPayments = lazy(() => import('./pages/AdminPayments'));
+const AdminPaymentMethods = lazy(() => import('./pages/AdminPaymentMethods'));
+const AdminPromoOffers = lazy(() => import('./pages/AdminPromoOffers'));
+const AdminRemnawave = lazy(() => import('./pages/AdminRemnawave'));
+const AdminEmailTemplates = lazy(() => import('./pages/AdminEmailTemplates'));
function ProtectedRoute({ children }: { children: React.ReactNode }) {
- const { isAuthenticated, isLoading } = useAuthStore()
- const location = useLocation()
+ const { isAuthenticated, isLoading } = useAuthStore();
+ const location = useLocation();
if (isLoading) {
- return
+ return ;
}
if (!isAuthenticated) {
// Сохраняем текущий URL для возврата после авторизации
- saveReturnUrl()
- return
+ saveReturnUrl();
+ return ;
}
- return {children}
+ return {children};
}
function AdminRoute({ children }: { children: React.ReactNode }) {
- const { isAuthenticated, isLoading, isAdmin } = useAuthStore()
- const location = useLocation()
+ const { isAuthenticated, isLoading, isAdmin } = useAuthStore();
+ const location = useLocation();
if (isLoading) {
- return
+ return ;
}
if (!isAuthenticated) {
// Сохраняем текущий URL для возврата после авторизации
- saveReturnUrl()
- return
+ saveReturnUrl();
+ return ;
}
if (!isAdmin) {
- return
+ return ;
}
- return {children}
+ return {children};
}
// Suspense wrapper for lazy components
function LazyPage({ children }: { children: React.ReactNode }) {
- return (
- }>
- {children}
-
- )
+ return }>{children};
}
function BlockingOverlay() {
- const { blockingType } = useBlockingStore()
+ const { blockingType } = useBlockingStore();
if (blockingType === 'maintenance') {
- return
+ return ;
}
if (blockingType === 'channel_subscription') {
- return
+ return ;
}
- return null
+ return null;
}
function App() {
- useAnalyticsCounters()
+ useAnalyticsCounters();
return (
<>
- {/* Public routes */}
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ {/* Public routes */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
- {/* Protected routes */}
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
+ {/* Protected routes */}
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
- {/* Admin routes */}
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
+ {/* Admin routes */}
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
- {/* Catch all */}
- } />
-
+ {/* Catch all */}
+ } />
+
>
- )
+ );
}
-export default App
+export default App;
diff --git a/src/api/admin.ts b/src/api/admin.ts
index 1bd79fe..1d5faa4 100644
--- a/src/api/admin.ts
+++ b/src/api/admin.ts
@@ -1,306 +1,310 @@
-import apiClient from './client'
+import apiClient from './client';
export interface AdminTicketUser {
- id: number
- telegram_id: number
- username: string | null
- first_name: string | null
- last_name: string | null
+ id: number;
+ telegram_id: number;
+ username: string | null;
+ first_name: string | null;
+ last_name: string | null;
}
export interface AdminTicketMessage {
- id: number
- message_text: string
- is_from_admin: boolean
- has_media: boolean
- media_type: string | null
- media_file_id: string | null
- media_caption: string | null
- created_at: string
+ id: number;
+ message_text: string;
+ is_from_admin: boolean;
+ has_media: boolean;
+ media_type: string | null;
+ media_file_id: string | null;
+ media_caption: string | null;
+ created_at: string;
}
export interface AdminTicket {
- id: number
- title: string
- status: string
- priority: string
- created_at: string
- updated_at: string
- closed_at: string | null
- messages_count: number
- user: AdminTicketUser | null
- last_message: AdminTicketMessage | null
+ id: number;
+ title: string;
+ status: string;
+ priority: string;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ messages_count: number;
+ user: AdminTicketUser | null;
+ last_message: AdminTicketMessage | null;
}
export interface AdminTicketDetail {
- id: number
- title: string
- status: string
- priority: string
- created_at: string
- updated_at: string
- closed_at: string | null
- is_reply_blocked: boolean
- user: AdminTicketUser | null
- messages: AdminTicketMessage[]
+ id: number;
+ title: string;
+ status: string;
+ priority: string;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ is_reply_blocked: boolean;
+ user: AdminTicketUser | null;
+ messages: AdminTicketMessage[];
}
export interface AdminTicketStats {
- total: number
- open: number
- pending: number
- answered: number
- closed: number
+ total: number;
+ open: number;
+ pending: number;
+ answered: number;
+ closed: number;
}
export interface TicketSettings {
- sla_enabled: boolean
- sla_minutes: number
- sla_check_interval_seconds: number
- sla_reminder_cooldown_minutes: number
- support_system_mode: string // tickets, contact, both
- cabinet_user_notifications_enabled: boolean
- cabinet_admin_notifications_enabled: boolean
+ sla_enabled: boolean;
+ sla_minutes: number;
+ sla_check_interval_seconds: number;
+ sla_reminder_cooldown_minutes: number;
+ support_system_mode: string; // tickets, contact, both
+ cabinet_user_notifications_enabled: boolean;
+ cabinet_admin_notifications_enabled: boolean;
}
export interface TicketSettingsUpdate {
- sla_enabled?: boolean
- sla_minutes?: number
- sla_check_interval_seconds?: number
- sla_reminder_cooldown_minutes?: number
- support_system_mode?: string
- cabinet_user_notifications_enabled?: boolean
- cabinet_admin_notifications_enabled?: boolean
+ sla_enabled?: boolean;
+ sla_minutes?: number;
+ sla_check_interval_seconds?: number;
+ sla_reminder_cooldown_minutes?: number;
+ support_system_mode?: string;
+ cabinet_user_notifications_enabled?: boolean;
+ cabinet_admin_notifications_enabled?: boolean;
}
export interface AdminTicketListResponse {
- items: AdminTicket[]
- total: number
- page: number
- per_page: number
- pages: number
+ items: AdminTicket[];
+ total: number;
+ page: number;
+ per_page: number;
+ pages: number;
}
export const adminApi = {
// Check if current user is admin
checkIsAdmin: async (): Promise<{ is_admin: boolean }> => {
- const response = await apiClient.get('/cabinet/auth/me/is-admin')
- return response.data
+ const response = await apiClient.get('/cabinet/auth/me/is-admin');
+ return response.data;
},
// Get ticket statistics
getTicketStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/tickets/stats')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/tickets/stats');
+ return response.data;
},
// Get all tickets
- getTickets: async (params: {
- page?: number
- per_page?: number
- status?: string
- priority?: string
- } = {}): Promise => {
- const response = await apiClient.get('/cabinet/admin/tickets', { params })
- return response.data
+ getTickets: async (
+ params: {
+ page?: number;
+ per_page?: number;
+ status?: string;
+ priority?: string;
+ } = {},
+ ): Promise => {
+ const response = await apiClient.get('/cabinet/admin/tickets', { params });
+ return response.data;
},
// Get single ticket with messages
getTicket: async (ticketId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/tickets/${ticketId}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/tickets/${ticketId}`);
+ return response.data;
},
// Reply to ticket
replyToTicket: async (ticketId: number, message: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/reply`, { message })
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/reply`, { message });
+ return response.data;
},
// Update ticket status
updateTicketStatus: async (ticketId: number, status: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/status`, { status })
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/status`, { status });
+ return response.data;
},
// Update ticket priority
updateTicketPriority: async (ticketId: number, priority: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/priority`, { priority })
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/priority`, {
+ priority,
+ });
+ return response.data;
},
// Get ticket settings
getTicketSettings: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/tickets/settings')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/tickets/settings');
+ return response.data;
},
// Update ticket settings
updateTicketSettings: async (settings: TicketSettingsUpdate): Promise => {
- const response = await apiClient.patch('/cabinet/admin/tickets/settings', settings)
- return response.data
+ const response = await apiClient.patch('/cabinet/admin/tickets/settings', settings);
+ return response.data;
},
-}
+};
// ============ Dashboard Stats Types ============
export interface NodeStatus {
- uuid: string
- name: string
- address: string
- is_connected: boolean
- is_disabled: boolean
- users_online: number
- traffic_used_bytes?: number
- uptime?: string
- xray_version?: string
- node_version?: string
- last_status_message?: string
- xray_uptime?: string
- is_xray_running?: boolean
- cpu_count?: number
- cpu_model?: string
- total_ram?: string
- country_code?: string
+ uuid: string;
+ name: string;
+ address: string;
+ is_connected: boolean;
+ is_disabled: boolean;
+ users_online: number;
+ traffic_used_bytes?: number;
+ uptime?: string;
+ xray_version?: string;
+ node_version?: string;
+ last_status_message?: string;
+ xray_uptime?: string;
+ is_xray_running?: boolean;
+ cpu_count?: number;
+ cpu_model?: string;
+ total_ram?: string;
+ country_code?: string;
}
export interface NodesOverview {
- total: number
- online: number
- offline: number
- disabled: number
- total_users_online: number
- nodes: NodeStatus[]
+ total: number;
+ online: number;
+ offline: number;
+ disabled: number;
+ total_users_online: number;
+ nodes: NodeStatus[];
}
export interface RevenueData {
- date: string
- amount_kopeks: number
- amount_rubles: number
+ date: string;
+ amount_kopeks: number;
+ amount_rubles: number;
}
export interface SubscriptionStats {
- total: number
- active: number
- trial: number
- paid: number
- expired: number
- purchased_today: number
- purchased_week: number
- purchased_month: number
- trial_to_paid_conversion: number
+ total: number;
+ active: number;
+ trial: number;
+ paid: number;
+ expired: number;
+ purchased_today: number;
+ purchased_week: number;
+ purchased_month: number;
+ trial_to_paid_conversion: number;
}
export interface FinancialStats {
- income_today_kopeks: number
- income_today_rubles: number
- income_month_kopeks: number
- income_month_rubles: number
- income_total_kopeks: number
- income_total_rubles: number
- subscription_income_kopeks: number
- subscription_income_rubles: number
+ income_today_kopeks: number;
+ income_today_rubles: number;
+ income_month_kopeks: number;
+ income_month_rubles: number;
+ income_total_kopeks: number;
+ income_total_rubles: number;
+ subscription_income_kopeks: number;
+ subscription_income_rubles: number;
}
export interface ServerStats {
- total_servers: number
- available_servers: number
- servers_with_connections: number
- total_revenue_kopeks: number
- total_revenue_rubles: number
+ total_servers: number;
+ available_servers: number;
+ servers_with_connections: number;
+ total_revenue_kopeks: number;
+ total_revenue_rubles: number;
}
export interface TariffStatItem {
- tariff_id: number
- tariff_name: string
- active_subscriptions: number
- trial_subscriptions: number
- purchased_today: number
- purchased_week: number
- purchased_month: number
+ tariff_id: number;
+ tariff_name: string;
+ active_subscriptions: number;
+ trial_subscriptions: number;
+ purchased_today: number;
+ purchased_week: number;
+ purchased_month: number;
}
export interface TariffStats {
- tariffs: TariffStatItem[]
- total_tariff_subscriptions: number
+ tariffs: TariffStatItem[];
+ total_tariff_subscriptions: number;
}
export interface DashboardStats {
- nodes: NodesOverview
- subscriptions: SubscriptionStats
- financial: FinancialStats
- servers: ServerStats
- revenue_chart: RevenueData[]
- tariff_stats?: TariffStats
+ nodes: NodesOverview;
+ subscriptions: SubscriptionStats;
+ financial: FinancialStats;
+ servers: ServerStats;
+ revenue_chart: RevenueData[];
+ tariff_stats?: TariffStats;
}
// ============ Extended Stats Types ============
export interface TopReferrerItem {
- user_id: number
- telegram_id: number
- username?: string
- display_name: string
- invited_count: number
- invited_today: number
- invited_week: number
- invited_month: number
- earnings_today_kopeks: number
- earnings_week_kopeks: number
- earnings_month_kopeks: number
- earnings_total_kopeks: number
+ user_id: number;
+ telegram_id: number;
+ username?: string;
+ display_name: string;
+ invited_count: number;
+ invited_today: number;
+ invited_week: number;
+ invited_month: number;
+ earnings_today_kopeks: number;
+ earnings_week_kopeks: number;
+ earnings_month_kopeks: number;
+ earnings_total_kopeks: number;
}
export interface TopReferrersResponse {
- by_earnings: TopReferrerItem[]
- by_invited: TopReferrerItem[]
- total_referrers: number
- total_referrals: number
- total_earnings_kopeks: number
+ by_earnings: TopReferrerItem[];
+ by_invited: TopReferrerItem[];
+ total_referrers: number;
+ total_referrals: number;
+ total_earnings_kopeks: number;
}
export interface TopCampaignItem {
- id: number
- name: string
- start_parameter: string
- bonus_type: string
- is_active: boolean
- registrations: number
- conversions: number
- conversion_rate: number
- total_revenue_kopeks: number
- avg_revenue_per_user_kopeks: number
- created_at?: string
+ id: number;
+ name: string;
+ start_parameter: string;
+ bonus_type: string;
+ is_active: boolean;
+ registrations: number;
+ conversions: number;
+ conversion_rate: number;
+ total_revenue_kopeks: number;
+ avg_revenue_per_user_kopeks: number;
+ created_at?: string;
}
export interface TopCampaignsResponse {
- campaigns: TopCampaignItem[]
- total_campaigns: number
- total_registrations: number
- total_revenue_kopeks: number
+ campaigns: TopCampaignItem[];
+ total_campaigns: number;
+ total_registrations: number;
+ total_revenue_kopeks: number;
}
export interface RecentPaymentItem {
- id: number
- user_id: number
- telegram_id: number
- username?: string
- display_name: string
- amount_kopeks: number
- amount_rubles: number
- type: string
- type_display: string
- payment_method?: string
- description?: string
- created_at: string
- is_completed: boolean
+ id: number;
+ user_id: number;
+ telegram_id: number;
+ username?: string;
+ display_name: string;
+ amount_kopeks: number;
+ amount_rubles: number;
+ type: string;
+ type_display: string;
+ payment_method?: string;
+ description?: string;
+ created_at: string;
+ is_completed: boolean;
}
export interface RecentPaymentsResponse {
- payments: RecentPaymentItem[]
- total_count: number
- total_today_kopeks: number
- total_week_kopeks: number
+ payments: RecentPaymentItem[];
+ total_count: number;
+ total_today_kopeks: number;
+ total_week_kopeks: number;
}
// ============ Dashboard Stats API ============
@@ -308,43 +312,51 @@ export interface RecentPaymentsResponse {
export const statsApi = {
// Get complete dashboard stats
getDashboardStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/dashboard')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/dashboard');
+ return response.data;
},
// Get nodes status
getNodesStatus: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/nodes')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/nodes');
+ return response.data;
},
// Restart a node
restartNode: async (nodeUuid: string): Promise<{ success: boolean; message: string }> => {
- const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/restart`)
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/restart`);
+ return response.data;
},
// Toggle node (enable/disable)
- toggleNode: async (nodeUuid: string): Promise<{ success: boolean; message: string; is_disabled: boolean }> => {
- const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/toggle`)
- return response.data
+ toggleNode: async (
+ nodeUuid: string,
+ ): Promise<{ success: boolean; message: string; is_disabled: boolean }> => {
+ const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/toggle`);
+ return response.data;
},
// Get top referrers
getTopReferrers: async (limit: number = 20): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/referrals/top', { params: { limit } })
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/referrals/top', {
+ params: { limit },
+ });
+ return response.data;
},
// Get top campaigns
getTopCampaigns: async (limit: number = 20): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/campaigns/top', { params: { limit } })
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/campaigns/top', {
+ params: { limit },
+ });
+ return response.data;
},
// Get recent payments
getRecentPayments: async (limit: number = 50): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/payments/recent', { params: { limit } })
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/payments/recent', {
+ params: { limit },
+ });
+ return response.data;
},
-}
+};
diff --git a/src/api/adminApps.ts b/src/api/adminApps.ts
index b57a78c..414f7c4 100644
--- a/src/api/adminApps.ts
+++ b/src/api/adminApps.ts
@@ -1,220 +1,240 @@
-import apiClient from './client'
+import apiClient from './client';
export interface LocalizedText {
- en: string
- ru: string
- zh?: string
- fa?: string
- fr?: string
+ en: string;
+ ru: string;
+ zh?: string;
+ fa?: string;
+ fr?: string;
}
export interface AppButton {
- id?: string // Unique identifier for React key (client-side only)
- buttonLink: string
- buttonText: LocalizedText
+ id?: string; // Unique identifier for React key (client-side only)
+ buttonLink: string;
+ buttonText: LocalizedText;
}
export interface AppStep {
- description: LocalizedText
- buttons?: AppButton[]
- title?: LocalizedText
+ description: LocalizedText;
+ buttons?: AppButton[];
+ title?: LocalizedText;
}
export interface AppDefinition {
- id: string
- name: string
- isFeatured: boolean
- urlScheme: string
- isNeedBase64Encoding?: boolean
- installationStep: AppStep
- addSubscriptionStep: AppStep
- connectAndUseStep: AppStep
- additionalBeforeAddSubscriptionStep?: AppStep
- additionalAfterAddSubscriptionStep?: AppStep
+ id: string;
+ name: string;
+ isFeatured: boolean;
+ urlScheme: string;
+ isNeedBase64Encoding?: boolean;
+ installationStep: AppStep;
+ addSubscriptionStep: AppStep;
+ connectAndUseStep: AppStep;
+ additionalBeforeAddSubscriptionStep?: AppStep;
+ additionalAfterAddSubscriptionStep?: AppStep;
}
export interface AppConfigBranding {
- name: string
- logoUrl: string
- supportUrl: string
+ name: string;
+ logoUrl: string;
+ supportUrl: string;
}
export interface AppConfigConfig {
- additionalLocales: string[]
- branding: AppConfigBranding
+ additionalLocales: string[];
+ branding: AppConfigBranding;
}
export interface AppConfigResponse {
- config: AppConfigConfig
- platforms: Record
+ config: AppConfigConfig;
+ platforms: Record;
}
export const adminAppsApi = {
// Get full app config
getConfig: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/apps')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/apps');
+ return response.data;
},
// Get available platforms
getPlatforms: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/apps/platforms')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/apps/platforms');
+ return response.data;
},
// Get apps for a platform
getPlatformApps: async (platform: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/apps/platforms/${platform}`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/apps/platforms/${platform}`,
+ );
+ return response.data;
},
// Create a new app
createApp: async (platform: string, app: AppDefinition): Promise => {
- const response = await apiClient.post(`/cabinet/admin/apps/platforms/${platform}`, {
- platform,
- app,
- })
- return response.data
+ const response = await apiClient.post(
+ `/cabinet/admin/apps/platforms/${platform}`,
+ {
+ platform,
+ app,
+ },
+ );
+ return response.data;
},
// Update an app
- updateApp: async (platform: string, appId: string, app: AppDefinition): Promise => {
- const response = await apiClient.put(`/cabinet/admin/apps/platforms/${platform}/${appId}`, {
- app,
- })
- return response.data
+ updateApp: async (
+ platform: string,
+ appId: string,
+ app: AppDefinition,
+ ): Promise => {
+ const response = await apiClient.put(
+ `/cabinet/admin/apps/platforms/${platform}/${appId}`,
+ {
+ app,
+ },
+ );
+ return response.data;
},
// Delete an app
deleteApp: async (platform: string, appId: string): Promise => {
- await apiClient.delete(`/cabinet/admin/apps/platforms/${platform}/${appId}`)
+ await apiClient.delete(`/cabinet/admin/apps/platforms/${platform}/${appId}`);
},
// Reorder apps
reorderApps: async (platform: string, appIds: string[]): Promise => {
await apiClient.post(`/cabinet/admin/apps/platforms/${platform}/reorder`, {
app_ids: appIds,
- })
+ });
},
// Copy app to another platform
- copyApp: async (platform: string, appId: string, targetPlatform: string): Promise<{ new_id: string }> => {
+ copyApp: async (
+ platform: string,
+ appId: string,
+ targetPlatform: string,
+ ): Promise<{ new_id: string }> => {
const response = await apiClient.post<{ new_id: string; target_platform: string }>(
- `/cabinet/admin/apps/platforms/${platform}/copy/${appId}?target_platform=${targetPlatform}`
- )
- return response.data
+ `/cabinet/admin/apps/platforms/${platform}/copy/${appId}?target_platform=${targetPlatform}`,
+ );
+ return response.data;
},
// Get branding
getBranding: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/apps/branding')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/apps/branding');
+ return response.data;
},
// Update branding
updateBranding: async (branding: AppConfigBranding): Promise => {
const response = await apiClient.put('/cabinet/admin/apps/branding', {
branding,
- })
- return response.data
+ });
+ return response.data;
},
// Get RemnaWave config status
getRemnaWaveStatus: async (): Promise<{ enabled: boolean; config_uuid: string | null }> => {
const response = await apiClient.get<{ enabled: boolean; config_uuid: string | null }>(
- '/cabinet/admin/apps/remnawave/status'
- )
- return response.data
+ '/cabinet/admin/apps/remnawave/status',
+ );
+ return response.data;
},
// Set RemnaWave config UUID
- setRemnaWaveUuid: async (uuid: string | null): Promise<{ enabled: boolean; config_uuid: string | null }> => {
+ setRemnaWaveUuid: async (
+ uuid: string | null,
+ ): Promise<{ enabled: boolean; config_uuid: string | null }> => {
const response = await apiClient.put<{ enabled: boolean; config_uuid: string | null }>(
'/cabinet/admin/apps/remnawave/uuid',
- { uuid }
- )
- return response.data
+ { uuid },
+ );
+ return response.data;
},
// List available RemnaWave configs
- listRemnaWaveConfigs: async (): Promise<{ uuid: string; name: string; view_position: number }[]> => {
+ listRemnaWaveConfigs: async (): Promise<
+ { uuid: string; name: string; view_position: number }[]
+ > => {
const response = await apiClient.get<{ uuid: string; name: string; view_position: number }[]>(
- '/cabinet/admin/apps/remnawave/configs'
- )
- return response.data
+ '/cabinet/admin/apps/remnawave/configs',
+ );
+ return response.data;
},
// Get RemnaWave subscription config
getRemnaWaveConfig: async (): Promise => {
const response = await apiClient.get<{
- uuid: string
- name: string
- view_position: number
- config: RemnawaveConfig
- }>('/cabinet/admin/apps/remnawave/config')
- return response.data.config
+ uuid: string;
+ name: string;
+ view_position: number;
+ config: RemnawaveConfig;
+ }>('/cabinet/admin/apps/remnawave/config');
+ return response.data.config;
},
-}
+};
// ============== RemnaWave Format Types ==============
export interface RemnawaveButton {
- url: string
- text: LocalizedText
+ url: string;
+ text: LocalizedText;
}
export interface RemnawaveBlock {
- title: LocalizedText
- description: LocalizedText
- buttons?: RemnawaveButton[]
- svgIconKey?: string
- svgIconColor?: string
+ title: LocalizedText;
+ description: LocalizedText;
+ buttons?: RemnawaveButton[];
+ svgIconKey?: string;
+ svgIconColor?: string;
}
export interface RemnawaveApp {
- name: string
- featured?: boolean
- urlScheme?: string
- isNeedBase64Encoding?: boolean
- blocks: RemnawaveBlock[]
+ name: string;
+ featured?: boolean;
+ urlScheme?: string;
+ isNeedBase64Encoding?: boolean;
+ blocks: RemnawaveBlock[];
}
export interface RemnawavePlatform {
- apps: RemnawaveApp[]
+ apps: RemnawaveApp[];
}
export interface RemnawaveSvgItem {
- svgString: string
- tags?: string[]
+ svgString: string;
+ tags?: string[];
}
export interface RemnawaveBaseSettings {
- isShowTutorialButton: boolean
- tutorialUrl: string
+ isShowTutorialButton: boolean;
+ tutorialUrl: string;
}
export interface RemnawaveBaseTranslations {
- installApp: LocalizedText
- addSubscription: LocalizedText
- connectAndUse: LocalizedText
- copyLink: LocalizedText
- openApp: LocalizedText
- tutorial: LocalizedText
- close: LocalizedText
+ installApp: LocalizedText;
+ addSubscription: LocalizedText;
+ connectAndUse: LocalizedText;
+ copyLink: LocalizedText;
+ openApp: LocalizedText;
+ tutorial: LocalizedText;
+ close: LocalizedText;
}
export interface RemnawaveBrandingSettings {
- name: string
- logoUrl: string
- supportUrl: string
+ name: string;
+ logoUrl: string;
+ supportUrl: string;
}
export interface RemnawaveConfig {
- platforms: Record
- svgLibrary?: Record
- baseSettings?: RemnawaveBaseSettings
- baseTranslations?: RemnawaveBaseTranslations
- brandingSettings?: RemnawaveBrandingSettings
+ platforms: Record;
+ svgLibrary?: Record;
+ baseSettings?: RemnawaveBaseSettings;
+ baseTranslations?: RemnawaveBaseTranslations;
+ brandingSettings?: RemnawaveBrandingSettings;
}
// ============== Converter Functions ==============
@@ -225,23 +245,29 @@ const emptyLocalizedText = (): LocalizedText => ({
zh: '',
fa: '',
fr: '',
-})
+});
// Convert Cabinet button to RemnaWave button
const cabinetButtonToRemnawave = (button: AppButton): RemnawaveButton => ({
url: button.buttonLink,
text: { ...emptyLocalizedText(), ...button.buttonText },
-})
+});
// Convert RemnaWave button to Cabinet button
const remnawaveButtonToCabinet = (button: RemnawaveButton): AppButton => ({
buttonLink: button.url,
- buttonText: { en: button.text.en || '', ru: button.text.ru || '', zh: button.text.zh, fa: button.text.fa, fr: button.text.fr },
-})
+ buttonText: {
+ en: button.text.en || '',
+ ru: button.text.ru || '',
+ zh: button.text.zh,
+ fa: button.text.fa,
+ fr: button.text.fr,
+ },
+});
// Convert Cabinet app to RemnaWave app format
export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
- const blocks: RemnawaveBlock[] = []
+ const blocks: RemnawaveBlock[] = [];
// Block 1: Installation
blocks.push({
@@ -250,17 +276,27 @@ export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
buttons: app.installationStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'download',
svgIconColor: '#3B82F6',
- })
+ });
// Block 2 (optional): Additional before subscription
- if (app.additionalBeforeAddSubscriptionStep?.description?.en || app.additionalBeforeAddSubscriptionStep?.description?.ru) {
+ if (
+ app.additionalBeforeAddSubscriptionStep?.description?.en ||
+ app.additionalBeforeAddSubscriptionStep?.description?.ru
+ ) {
blocks.push({
- title: app.additionalBeforeAddSubscriptionStep.title || { ...emptyLocalizedText(), en: 'Preparation', ru: 'Подготовка' },
- description: { ...emptyLocalizedText(), ...app.additionalBeforeAddSubscriptionStep.description },
+ title: app.additionalBeforeAddSubscriptionStep.title || {
+ ...emptyLocalizedText(),
+ en: 'Preparation',
+ ru: 'Подготовка',
+ },
+ description: {
+ ...emptyLocalizedText(),
+ ...app.additionalBeforeAddSubscriptionStep.description,
+ },
buttons: app.additionalBeforeAddSubscriptionStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'settings',
svgIconColor: '#8B5CF6',
- })
+ });
}
// Block 3: Add subscription
@@ -270,17 +306,27 @@ export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
buttons: app.addSubscriptionStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'plus',
svgIconColor: '#10B981',
- })
+ });
// Block 4 (optional): Additional after subscription
- if (app.additionalAfterAddSubscriptionStep?.description?.en || app.additionalAfterAddSubscriptionStep?.description?.ru) {
+ if (
+ app.additionalAfterAddSubscriptionStep?.description?.en ||
+ app.additionalAfterAddSubscriptionStep?.description?.ru
+ ) {
blocks.push({
- title: app.additionalAfterAddSubscriptionStep.title || { ...emptyLocalizedText(), en: 'Configuration', ru: 'Настройка' },
- description: { ...emptyLocalizedText(), ...app.additionalAfterAddSubscriptionStep.description },
+ title: app.additionalAfterAddSubscriptionStep.title || {
+ ...emptyLocalizedText(),
+ en: 'Configuration',
+ ru: 'Настройка',
+ },
+ description: {
+ ...emptyLocalizedText(),
+ ...app.additionalAfterAddSubscriptionStep.description,
+ },
buttons: app.additionalAfterAddSubscriptionStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'settings',
svgIconColor: '#F59E0B',
- })
+ });
}
// Block 5: Connect and use
@@ -290,7 +336,7 @@ export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
buttons: app.connectAndUseStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'check',
svgIconColor: '#22C55E',
- })
+ });
return {
name: app.name,
@@ -298,78 +344,104 @@ export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
urlScheme: app.urlScheme,
isNeedBase64Encoding: app.isNeedBase64Encoding,
blocks,
- }
-}
+ };
+};
// Convert RemnaWave app to Cabinet app format
export const remnawaveAppToCabinet = (app: RemnawaveApp, platform: string): AppDefinition => {
- const blocks = app.blocks || []
+ const blocks = app.blocks || [];
// Default empty step
const emptyStep = (): AppStep => ({
description: emptyLocalizedText(),
buttons: [],
- })
+ });
// Try to identify blocks by their titles or position
- let installationBlock: RemnawaveBlock | undefined
- let subscriptionBlock: RemnawaveBlock | undefined
- let connectBlock: RemnawaveBlock | undefined
- let beforeSubBlock: RemnawaveBlock | undefined
- let afterSubBlock: RemnawaveBlock | undefined
+ let installationBlock: RemnawaveBlock | undefined;
+ let subscriptionBlock: RemnawaveBlock | undefined;
+ let connectBlock: RemnawaveBlock | undefined;
+ let beforeSubBlock: RemnawaveBlock | undefined;
+ let afterSubBlock: RemnawaveBlock | undefined;
// First pass: try to identify by title keywords
for (const block of blocks) {
- const enTitle = (block.title?.en || '').toLowerCase()
- const ruTitle = (block.title?.ru || '').toLowerCase()
+ const enTitle = (block.title?.en || '').toLowerCase();
+ const ruTitle = (block.title?.ru || '').toLowerCase();
if (enTitle.includes('install') || ruTitle.includes('установ') || ruTitle.includes('скачай')) {
- if (!installationBlock) installationBlock = block
- } else if (enTitle.includes('subscription') || enTitle.includes('add') || ruTitle.includes('подписк') || ruTitle.includes('добав')) {
- if (!subscriptionBlock) subscriptionBlock = block
- } else if (enTitle.includes('connect') || enTitle.includes('use') || ruTitle.includes('подключ') || ruTitle.includes('использ')) {
- if (!connectBlock) connectBlock = block
+ if (!installationBlock) installationBlock = block;
+ } else if (
+ enTitle.includes('subscription') ||
+ enTitle.includes('add') ||
+ ruTitle.includes('подписк') ||
+ ruTitle.includes('добав')
+ ) {
+ if (!subscriptionBlock) subscriptionBlock = block;
+ } else if (
+ enTitle.includes('connect') ||
+ enTitle.includes('use') ||
+ ruTitle.includes('подключ') ||
+ ruTitle.includes('использ')
+ ) {
+ if (!connectBlock) connectBlock = block;
}
}
// Second pass: assign remaining blocks by position if not found by title
if (!installationBlock && blocks.length > 0) {
- installationBlock = blocks[0]
+ installationBlock = blocks[0];
}
if (!subscriptionBlock && blocks.length > 1) {
- subscriptionBlock = blocks.find(b => b !== installationBlock) || blocks[1]
+ subscriptionBlock = blocks.find((b) => b !== installationBlock) || blocks[1];
}
if (!connectBlock && blocks.length > 2) {
- connectBlock = blocks.find(b => b !== installationBlock && b !== subscriptionBlock) || blocks[blocks.length - 1]
+ connectBlock =
+ blocks.find((b) => b !== installationBlock && b !== subscriptionBlock) ||
+ blocks[blocks.length - 1];
}
// Assign additional blocks
- const additionalBlocks = blocks.filter(b =>
- b !== installationBlock && b !== subscriptionBlock && b !== connectBlock
- )
+ const additionalBlocks = blocks.filter(
+ (b) => b !== installationBlock && b !== subscriptionBlock && b !== connectBlock,
+ );
if (additionalBlocks.length >= 1) {
// Check if block appears before subscription
- const subIndex = blocks.indexOf(subscriptionBlock!)
- const firstAdditionalIndex = blocks.indexOf(additionalBlocks[0])
+ const subIndex = blocks.indexOf(subscriptionBlock!);
+ const firstAdditionalIndex = blocks.indexOf(additionalBlocks[0]);
if (firstAdditionalIndex < subIndex) {
- beforeSubBlock = additionalBlocks[0]
+ beforeSubBlock = additionalBlocks[0];
if (additionalBlocks.length >= 2) {
- afterSubBlock = additionalBlocks[1]
+ afterSubBlock = additionalBlocks[1];
}
} else {
- afterSubBlock = additionalBlocks[0]
+ afterSubBlock = additionalBlocks[0];
}
}
// Convert block to cabinet step
const blockToStep = (block: RemnawaveBlock | undefined): AppStep => {
- if (!block) return emptyStep()
+ if (!block) return emptyStep();
return {
- description: { en: block.description?.en || '', ru: block.description?.ru || '', zh: block.description?.zh, fa: block.description?.fa, fr: block.description?.fr },
- title: block.title ? { en: block.title.en || '', ru: block.title.ru || '', zh: block.title.zh, fa: block.title.fa, fr: block.title.fr } : undefined,
+ description: {
+ en: block.description?.en || '',
+ ru: block.description?.ru || '',
+ zh: block.description?.zh,
+ fa: block.description?.fa,
+ fr: block.description?.fr,
+ },
+ title: block.title
+ ? {
+ en: block.title.en || '',
+ ru: block.title.ru || '',
+ zh: block.title.zh,
+ fa: block.title.fa,
+ fr: block.title.fr,
+ }
+ : undefined,
buttons: block.buttons?.map(remnawaveButtonToCabinet),
- }
- }
+ };
+ };
return {
id: `${app.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${platform}-${Date.now()}`,
@@ -382,66 +454,114 @@ export const remnawaveAppToCabinet = (app: RemnawaveApp, platform: string): AppD
connectAndUseStep: blockToStep(connectBlock),
additionalBeforeAddSubscriptionStep: beforeSubBlock ? blockToStep(beforeSubBlock) : undefined,
additionalAfterAddSubscriptionStep: afterSubBlock ? blockToStep(afterSubBlock) : undefined,
- }
-}
+ };
+};
// Export all apps from cabinet to RemnaWave format
export const exportToRemnawaveFormat = (
platformApps: Record,
- branding?: AppConfigBranding
+ branding?: AppConfigBranding,
): RemnawaveConfig => {
- const platforms: Record = {}
+ const platforms: Record = {};
for (const [platform, apps] of Object.entries(platformApps)) {
platforms[platform] = {
apps: apps.map(cabinetAppToRemnawave),
- }
+ };
}
return {
platforms,
svgLibrary: {
- download: { svgString: '' },
- plus: { svgString: '' },
- check: { svgString: '' },
- settings: { svgString: '' },
+ download: {
+ svgString:
+ '',
+ },
+ plus: {
+ svgString:
+ '',
+ },
+ check: {
+ svgString:
+ '',
+ },
+ settings: {
+ svgString:
+ '',
+ },
},
baseSettings: {
isShowTutorialButton: false,
tutorialUrl: '',
},
baseTranslations: {
- installApp: { en: 'Install App', ru: 'Установить приложение', zh: '安装应用', fa: 'نصب برنامه', fr: 'Installer l\'application' },
- addSubscription: { en: 'Add Subscription', ru: 'Добавить подписку', zh: '添加订阅', fa: 'اضافه کردن اشتراک', fr: 'Ajouter un abonnement' },
- connectAndUse: { en: 'Connect and Use', ru: 'Подключиться и использовать', zh: '连接并使用', fa: 'اتصال و استفاده', fr: 'Connecter et utiliser' },
- copyLink: { en: 'Copy Link', ru: 'Скопировать ссылку', zh: '复制链接', fa: 'کپی لینک', fr: 'Copier le lien' },
- openApp: { en: 'Open App', ru: 'Открыть приложение', zh: '打开应用', fa: 'باز کردن برنامه', fr: 'Ouvrir l\'application' },
+ installApp: {
+ en: 'Install App',
+ ru: 'Установить приложение',
+ zh: '安装应用',
+ fa: 'نصب برنامه',
+ fr: "Installer l'application",
+ },
+ addSubscription: {
+ en: 'Add Subscription',
+ ru: 'Добавить подписку',
+ zh: '添加订阅',
+ fa: 'اضافه کردن اشتراک',
+ fr: 'Ajouter un abonnement',
+ },
+ connectAndUse: {
+ en: 'Connect and Use',
+ ru: 'Подключиться и использовать',
+ zh: '连接并使用',
+ fa: 'اتصال و استفاده',
+ fr: 'Connecter et utiliser',
+ },
+ copyLink: {
+ en: 'Copy Link',
+ ru: 'Скопировать ссылку',
+ zh: '复制链接',
+ fa: 'کپی لینک',
+ fr: 'Copier le lien',
+ },
+ openApp: {
+ en: 'Open App',
+ ru: 'Открыть приложение',
+ zh: '打开应用',
+ fa: 'باز کردن برنامه',
+ fr: "Ouvrir l'application",
+ },
tutorial: { en: 'Tutorial', ru: 'Инструкция', zh: '教程', fa: 'آموزش', fr: 'Tutoriel' },
close: { en: 'Close', ru: 'Закрыть', zh: '关闭', fa: 'بستن', fr: 'Fermer' },
},
- brandingSettings: branding ? {
- name: branding.name,
- logoUrl: branding.logoUrl,
- supportUrl: branding.supportUrl,
- } : undefined,
- }
-}
+ brandingSettings: branding
+ ? {
+ name: branding.name,
+ logoUrl: branding.logoUrl,
+ supportUrl: branding.supportUrl,
+ }
+ : undefined,
+ };
+};
// Import apps from RemnaWave format to cabinet
export const importFromRemnawaveFormat = (
- config: RemnawaveConfig
+ config: RemnawaveConfig,
): { platformApps: Record; branding?: AppConfigBranding } => {
- const platformApps: Record = {}
+ const platformApps: Record = {};
for (const [platform, platformData] of Object.entries(config.platforms || {})) {
- platformApps[platform] = (platformData.apps || []).map(app => remnawaveAppToCabinet(app, platform))
+ platformApps[platform] = (platformData.apps || []).map((app) =>
+ remnawaveAppToCabinet(app, platform),
+ );
}
- const branding = config.brandingSettings ? {
- name: config.brandingSettings.name,
- logoUrl: config.brandingSettings.logoUrl,
- supportUrl: config.brandingSettings.supportUrl,
- } : undefined
+ const branding = config.brandingSettings
+ ? {
+ name: config.brandingSettings.name,
+ logoUrl: config.brandingSettings.logoUrl,
+ supportUrl: config.brandingSettings.supportUrl,
+ }
+ : undefined;
- return { platformApps, branding }
-}
+ return { platformApps, branding };
+};
diff --git a/src/api/adminBroadcasts.ts b/src/api/adminBroadcasts.ts
index 43a7df4..6f60fc0 100644
--- a/src/api/adminBroadcasts.ts
+++ b/src/api/adminBroadcasts.ts
@@ -1,168 +1,187 @@
-import apiClient from './client'
+import apiClient from './client';
// Types
export interface BroadcastFilter {
- key: string
- label: string
- count: number | null
- group: string | null
+ key: string;
+ label: string;
+ count: number | null;
+ group: string | null;
}
export interface TariffFilter {
- key: string
- label: string
- tariff_id: number
- count: number
+ key: string;
+ label: string;
+ tariff_id: number;
+ count: number;
}
export interface BroadcastFiltersResponse {
- filters: BroadcastFilter[]
- tariff_filters: TariffFilter[]
- custom_filters: BroadcastFilter[]
+ filters: BroadcastFilter[];
+ tariff_filters: TariffFilter[];
+ custom_filters: BroadcastFilter[];
}
export interface TariffForBroadcast {
- id: number
- name: string
- filter_key: string
- active_users_count: number
+ id: number;
+ name: string;
+ filter_key: string;
+ active_users_count: number;
}
export interface BroadcastTariffsResponse {
- tariffs: TariffForBroadcast[]
+ tariffs: TariffForBroadcast[];
}
export interface BroadcastButton {
- key: string
- label: string
- default: boolean
+ key: string;
+ label: string;
+ default: boolean;
}
export interface BroadcastButtonsResponse {
- buttons: BroadcastButton[]
+ buttons: BroadcastButton[];
}
export interface BroadcastMedia {
- type: 'photo' | 'video' | 'document'
- file_id: string
- caption?: string
+ type: 'photo' | 'video' | 'document';
+ file_id: string;
+ caption?: string;
}
export interface BroadcastCreateRequest {
- target: string
- message_text: string
- selected_buttons: string[]
- media?: BroadcastMedia
+ target: string;
+ message_text: string;
+ selected_buttons: string[];
+ media?: BroadcastMedia;
}
export interface Broadcast {
- id: number
- target_type: string
- message_text: string
- has_media: boolean
- media_type: string | null
- media_file_id: string | null
- media_caption: string | null
- total_count: number
- sent_count: number
- failed_count: number
- status: 'queued' | 'in_progress' | 'completed' | 'partial' | 'failed' | 'cancelled' | 'cancelling'
- admin_id: number | null
- admin_name: string | null
- created_at: string
- completed_at: string | null
- progress_percent: number
+ id: number;
+ target_type: string;
+ message_text: string;
+ has_media: boolean;
+ media_type: string | null;
+ media_file_id: string | null;
+ media_caption: string | null;
+ total_count: number;
+ sent_count: number;
+ failed_count: number;
+ status:
+ | 'queued'
+ | 'in_progress'
+ | 'completed'
+ | 'partial'
+ | 'failed'
+ | 'cancelled'
+ | 'cancelling';
+ admin_id: number | null;
+ admin_name: string | null;
+ created_at: string;
+ completed_at: string | null;
+ progress_percent: number;
}
export interface BroadcastListResponse {
- items: Broadcast[]
- total: number
- limit: number
- offset: number
+ items: Broadcast[];
+ total: number;
+ limit: number;
+ offset: number;
}
export interface BroadcastPreviewRequest {
- target: string
+ target: string;
}
export interface BroadcastPreviewResponse {
- target: string
- count: number
+ target: string;
+ count: number;
}
export interface MediaUploadResponse {
- media_type: string
- file_id: string
- file_unique_id: string | null
- media_url: string
+ media_type: string;
+ file_id: string;
+ file_unique_id: string | null;
+ media_url: string;
}
export const adminBroadcastsApi = {
// Get all available filters with counts
getFilters: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/broadcasts/filters')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/broadcasts/filters',
+ );
+ return response.data;
},
// Get tariffs for filtering
getTariffs: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/broadcasts/tariffs')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/broadcasts/tariffs',
+ );
+ return response.data;
},
// Get available buttons
getButtons: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/broadcasts/buttons')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/broadcasts/buttons',
+ );
+ return response.data;
},
// Preview broadcast (get recipients count)
preview: async (target: string): Promise => {
- const response = await apiClient.post('/cabinet/admin/broadcasts/preview', {
- target,
- })
- return response.data
+ const response = await apiClient.post(
+ '/cabinet/admin/broadcasts/preview',
+ {
+ target,
+ },
+ );
+ return response.data;
},
// Create and start broadcast
create: async (data: BroadcastCreateRequest): Promise => {
- const response = await apiClient.post('/cabinet/admin/broadcasts', data)
- return response.data
+ const response = await apiClient.post('/cabinet/admin/broadcasts', data);
+ return response.data;
},
// Get list of broadcasts
list: async (limit = 20, offset = 0): Promise => {
const response = await apiClient.get('/cabinet/admin/broadcasts', {
params: { limit, offset },
- })
- return response.data
+ });
+ return response.data;
},
// Get broadcast details
get: async (id: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/broadcasts/${id}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/broadcasts/${id}`);
+ return response.data;
},
// Stop broadcast
stop: async (id: number): Promise => {
- const response = await apiClient.post(`/cabinet/admin/broadcasts/${id}/stop`)
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/broadcasts/${id}/stop`);
+ return response.data;
},
// Upload media (uses existing media endpoint)
- uploadMedia: async (file: File, mediaType: 'photo' | 'video' | 'document'): Promise => {
- const formData = new FormData()
- formData.append('file', file)
- formData.append('media_type', mediaType)
+ uploadMedia: async (
+ file: File,
+ mediaType: 'photo' | 'video' | 'document',
+ ): Promise => {
+ const formData = new FormData();
+ formData.append('file', file);
+ formData.append('media_type', mediaType);
const response = await apiClient.post('/cabinet/media/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
- })
- return response.data
+ });
+ return response.data;
},
-}
+};
-export default adminBroadcastsApi
+export default adminBroadcastsApi;
diff --git a/src/api/adminEmailTemplates.ts b/src/api/adminEmailTemplates.ts
index 4c0b225..0a0164d 100644
--- a/src/api/adminEmailTemplates.ts
+++ b/src/api/adminEmailTemplates.ts
@@ -1,85 +1,105 @@
-import apiClient from './client'
+import apiClient from './client';
export interface EmailTemplateLanguageStatus {
- has_custom: boolean
+ has_custom: boolean;
}
export interface EmailTemplateType {
- type: string
- label: Record
- description: Record
- context_vars: string[]
- languages: Record
+ type: string;
+ label: Record;
+ description: Record;
+ context_vars: string[];
+ languages: Record;
}
export interface EmailTemplateListResponse {
- items: EmailTemplateType[]
- available_languages: string[]
+ items: EmailTemplateType[];
+ available_languages: string[];
}
export interface EmailTemplateLanguageData {
- subject: string
- body_html: string
- is_default: boolean
- default_subject: string
- default_body_html: string
+ subject: string;
+ body_html: string;
+ is_default: boolean;
+ default_subject: string;
+ default_body_html: string;
}
export interface EmailTemplateDetail {
- notification_type: string
- label: Record
- description: Record
- context_vars: string[]
- languages: Record
+ notification_type: string;
+ label: Record;
+ description: Record;
+ context_vars: string[];
+ languages: Record;
}
export interface EmailTemplateUpdateRequest {
- subject: string
- body_html: string
+ subject: string;
+ body_html: string;
}
export interface EmailTemplatePreviewRequest {
- language: string
- subject?: string
- body_html?: string
+ language: string;
+ subject?: string;
+ body_html?: string;
}
export interface EmailTemplatePreviewResponse {
- subject: string
- body_html: string
+ subject: string;
+ body_html: string;
}
export interface EmailTemplateSendTestRequest {
- language: string
- email?: string
+ language: string;
+ email?: string;
}
export const adminEmailTemplatesApi = {
getTemplateTypes: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/email-templates')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/email-templates',
+ );
+ return response.data;
},
getTemplate: async (notificationType: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/email-templates/${notificationType}`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/email-templates/${notificationType}`,
+ );
+ return response.data;
},
- updateTemplate: async (notificationType: string, language: string, data: EmailTemplateUpdateRequest): Promise => {
- await apiClient.put(`/cabinet/admin/email-templates/${notificationType}/${language}`, data)
+ updateTemplate: async (
+ notificationType: string,
+ language: string,
+ data: EmailTemplateUpdateRequest,
+ ): Promise => {
+ await apiClient.put(`/cabinet/admin/email-templates/${notificationType}/${language}`, data);
},
deleteTemplate: async (notificationType: string, language: string): Promise => {
- await apiClient.delete(`/cabinet/admin/email-templates/${notificationType}/${language}`)
+ await apiClient.delete(`/cabinet/admin/email-templates/${notificationType}/${language}`);
},
- previewTemplate: async (notificationType: string, data: EmailTemplatePreviewRequest): Promise => {
- const response = await apiClient.post(`/cabinet/admin/email-templates/${notificationType}/preview`, data)
- return response.data
+ previewTemplate: async (
+ notificationType: string,
+ data: EmailTemplatePreviewRequest,
+ ): Promise => {
+ const response = await apiClient.post(
+ `/cabinet/admin/email-templates/${notificationType}/preview`,
+ data,
+ );
+ return response.data;
},
- sendTestEmail: async (notificationType: string, data: EmailTemplateSendTestRequest): Promise<{ sent_to: string }> => {
- const response = await apiClient.post<{ status: string; sent_to: string }>(`/cabinet/admin/email-templates/${notificationType}/test`, data)
- return response.data
+ sendTestEmail: async (
+ notificationType: string,
+ data: EmailTemplateSendTestRequest,
+ ): Promise<{ sent_to: string }> => {
+ const response = await apiClient.post<{ status: string; sent_to: string }>(
+ `/cabinet/admin/email-templates/${notificationType}/test`,
+ data,
+ );
+ return response.data;
},
-}
+};
diff --git a/src/api/adminPaymentMethods.ts b/src/api/adminPaymentMethods.ts
index 0a866d4..b8f83fc 100644
--- a/src/api/adminPaymentMethods.ts
+++ b/src/api/adminPaymentMethods.ts
@@ -1,31 +1,35 @@
-import apiClient from './client'
-import type { PaymentMethodConfig, PromoGroupSimple } from '../types'
+import apiClient from './client';
+import type { PaymentMethodConfig, PromoGroupSimple } from '../types';
export const adminPaymentMethodsApi = {
getAll: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/payment-methods')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/payment-methods');
+ return response.data;
},
getOne: async (methodId: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/payment-methods/${methodId}`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/payment-methods/${methodId}`,
+ );
+ return response.data;
},
update: async (methodId: string, data: Record): Promise => {
const response = await apiClient.put(
`/cabinet/admin/payment-methods/${methodId}`,
- data
- )
- return response.data
+ data,
+ );
+ return response.data;
},
updateOrder: async (methodIds: string[]): Promise => {
- await apiClient.put('/cabinet/admin/payment-methods/order', { method_ids: methodIds })
+ await apiClient.put('/cabinet/admin/payment-methods/order', { method_ids: methodIds });
},
getPromoGroups: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/payment-methods/promo-groups')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/payment-methods/promo-groups',
+ );
+ return response.data;
},
-}
+};
diff --git a/src/api/adminPayments.ts b/src/api/adminPayments.ts
index a169517..091e867 100644
--- a/src/api/adminPayments.ts
+++ b/src/api/adminPayments.ts
@@ -1,39 +1,46 @@
-import apiClient from './client'
-import type { PaginatedResponse, PendingPayment, ManualCheckResponse } from '../types'
+import apiClient from './client';
+import type { PaginatedResponse, PendingPayment, ManualCheckResponse } from '../types';
export interface PaymentsStats {
- total_pending: number
- by_method: Record
+ total_pending: number;
+ by_method: Record;
}
export const adminPaymentsApi = {
// Get all pending payments (admin)
getPendingPayments: async (params?: {
- page?: number
- per_page?: number
- method_filter?: string
+ page?: number;
+ per_page?: number;
+ method_filter?: string;
}): Promise> => {
- const response = await apiClient.get>('/cabinet/admin/payments', {
- params,
- })
- return response.data
+ const response = await apiClient.get>(
+ '/cabinet/admin/payments',
+ {
+ params,
+ },
+ );
+ return response.data;
},
// Get payments statistics
getStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/payments/stats')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/payments/stats');
+ return response.data;
},
// Get specific payment details
getPayment: async (method: string, paymentId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/payments/${method}/${paymentId}`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/payments/${method}/${paymentId}`,
+ );
+ return response.data;
},
// Manually check payment status
checkPaymentStatus: async (method: string, paymentId: number): Promise => {
- const response = await apiClient.post(`/cabinet/admin/payments/${method}/${paymentId}/check`)
- return response.data
+ const response = await apiClient.post(
+ `/cabinet/admin/payments/${method}/${paymentId}/check`,
+ );
+ return response.data;
},
-}
+};
diff --git a/src/api/adminRemnawave.ts b/src/api/adminRemnawave.ts
index a044e03..2043925 100644
--- a/src/api/adminRemnawave.ts
+++ b/src/api/adminRemnawave.ts
@@ -1,227 +1,227 @@
-import { apiClient } from './client'
+import { apiClient } from './client';
// ============ Types ============
// Status & Connection
export interface ConnectionStatus {
- status: string
- message: string
- api_url?: string
- status_code?: number
- system_info?: Record
+ status: string;
+ message: string;
+ api_url?: string;
+ status_code?: number;
+ system_info?: Record;
}
export interface RemnaWaveStatusResponse {
- is_configured: boolean
- configuration_error?: string
- connection?: ConnectionStatus
+ is_configured: boolean;
+ configuration_error?: string;
+ connection?: ConnectionStatus;
}
// System Statistics
export interface SystemSummary {
- users_online: number
- total_users: number
- active_connections: number
- nodes_online: number
- users_last_day: number
- users_last_week: number
- users_never_online: number
- total_user_traffic: number
+ users_online: number;
+ total_users: number;
+ active_connections: number;
+ nodes_online: number;
+ users_last_day: number;
+ users_last_week: number;
+ users_never_online: number;
+ total_user_traffic: number;
}
export interface ServerInfo {
- cpu_cores: number
- cpu_physical_cores: number
- memory_total: number
- memory_used: number
- memory_free: number
- memory_available: number
- uptime_seconds: number
+ cpu_cores: number;
+ cpu_physical_cores: number;
+ memory_total: number;
+ memory_used: number;
+ memory_free: number;
+ memory_available: number;
+ uptime_seconds: number;
}
export interface Bandwidth {
- realtime_download: number
- realtime_upload: number
- realtime_total: number
+ realtime_download: number;
+ realtime_upload: number;
+ realtime_total: number;
}
export interface TrafficPeriod {
- current: number
- previous: number
- difference?: string
+ current: number;
+ previous: number;
+ difference?: string;
}
export interface TrafficPeriods {
- last_2_days: TrafficPeriod
- last_7_days: TrafficPeriod
- last_30_days: TrafficPeriod
- current_month: TrafficPeriod
- current_year: TrafficPeriod
+ last_2_days: TrafficPeriod;
+ last_7_days: TrafficPeriod;
+ last_30_days: TrafficPeriod;
+ current_month: TrafficPeriod;
+ current_year: TrafficPeriod;
}
export interface SystemStatsResponse {
- system: SystemSummary
- users_by_status: Record
- server_info: ServerInfo
- bandwidth: Bandwidth
- traffic_periods: TrafficPeriods
- nodes_realtime: Record[]
- nodes_weekly: Record[]
- last_updated?: string
+ system: SystemSummary;
+ users_by_status: Record;
+ server_info: ServerInfo;
+ bandwidth: Bandwidth;
+ traffic_periods: TrafficPeriods;
+ nodes_realtime: Record[];
+ nodes_weekly: Record[];
+ last_updated?: string;
}
// Nodes
export interface NodeInfo {
- uuid: string
- name: string
- address: string
- country_code?: string
- is_connected: boolean
- is_disabled: boolean
- is_node_online: boolean
- is_xray_running: boolean
- users_online?: number
- traffic_used_bytes?: number
- traffic_limit_bytes?: number
- last_status_change?: string
- last_status_message?: string
- xray_uptime?: string
- is_traffic_tracking_active: boolean
- traffic_reset_day?: number
- notify_percent?: number
- consumption_multiplier: number
- cpu_count?: number
- cpu_model?: string
- total_ram?: string
- created_at?: string
- updated_at?: string
- provider_uuid?: string
+ uuid: string;
+ name: string;
+ address: string;
+ country_code?: string;
+ is_connected: boolean;
+ is_disabled: boolean;
+ is_node_online: boolean;
+ is_xray_running: boolean;
+ users_online?: number;
+ traffic_used_bytes?: number;
+ traffic_limit_bytes?: number;
+ last_status_change?: string;
+ last_status_message?: string;
+ xray_uptime?: string;
+ is_traffic_tracking_active: boolean;
+ traffic_reset_day?: number;
+ notify_percent?: number;
+ consumption_multiplier: number;
+ cpu_count?: number;
+ cpu_model?: string;
+ total_ram?: string;
+ created_at?: string;
+ updated_at?: string;
+ provider_uuid?: string;
}
export interface NodesListResponse {
- items: NodeInfo[]
- total: number
+ items: NodeInfo[];
+ total: number;
}
export interface NodesOverview {
- total: number
- online: number
- offline: number
- disabled: number
- total_users_online: number
- nodes: NodeInfo[]
+ total: number;
+ online: number;
+ offline: number;
+ disabled: number;
+ total_users_online: number;
+ nodes: NodeInfo[];
}
export interface NodeStatisticsResponse {
- node: NodeInfo
- realtime?: Record
- usage_history: Record[]
- last_updated?: string
+ node: NodeInfo;
+ realtime?: Record;
+ usage_history: Record[];
+ last_updated?: string;
}
export interface NodeActionResponse {
- success: boolean
- message?: string
- is_disabled?: boolean
+ success: boolean;
+ message?: string;
+ is_disabled?: boolean;
}
// Squads
export interface SquadWithLocalInfo {
- uuid: string
- name: string
- members_count: number
- inbounds_count: number
- inbounds: Record[]
- local_id?: number
- display_name?: string
- country_code?: string
- is_available?: boolean
- is_trial_eligible?: boolean
- price_kopeks?: number
- max_users?: number
- current_users?: number
- is_synced: boolean
+ uuid: string;
+ name: string;
+ members_count: number;
+ inbounds_count: number;
+ inbounds: Record[];
+ local_id?: number;
+ display_name?: string;
+ country_code?: string;
+ is_available?: boolean;
+ is_trial_eligible?: boolean;
+ price_kopeks?: number;
+ max_users?: number;
+ current_users?: number;
+ is_synced: boolean;
}
export interface SquadsListResponse {
- items: SquadWithLocalInfo[]
- total: number
+ items: SquadWithLocalInfo[];
+ total: number;
}
export interface SquadDetailResponse extends SquadWithLocalInfo {
- description?: string
- sort_order?: number
- active_subscriptions: number
+ description?: string;
+ sort_order?: number;
+ active_subscriptions: number;
}
export interface SquadOperationResponse {
- success: boolean
- message?: string
- data?: Record
+ success: boolean;
+ message?: string;
+ data?: Record;
}
// Migration
export interface MigrationPreviewResponse {
- squad_uuid: string
- squad_name: string
- current_users: number
- max_users?: number
- users_to_migrate: number
+ squad_uuid: string;
+ squad_name: string;
+ current_users: number;
+ max_users?: number;
+ users_to_migrate: number;
}
export interface MigrationStats {
- source_uuid: string
- target_uuid: string
- total: number
- updated: number
- panel_updated: number
- panel_failed: number
- source_removed: number
- target_added: number
+ source_uuid: string;
+ target_uuid: string;
+ total: number;
+ updated: number;
+ panel_updated: number;
+ panel_failed: number;
+ source_removed: number;
+ target_added: number;
}
export interface MigrationResponse {
- success: boolean
- message?: string
- error?: string
- data?: MigrationStats
+ success: boolean;
+ message?: string;
+ error?: string;
+ data?: MigrationStats;
}
// Inbounds
export interface InboundsListResponse {
- items: Record[]
- total: number
+ items: Record[];
+ total: number;
}
// Auto Sync
export interface AutoSyncStatus {
- enabled: boolean
- times: string[]
- next_run?: string
- is_running: boolean
- last_run_started_at?: string
- last_run_finished_at?: string
- last_run_success?: boolean
- last_run_reason?: string
- last_run_error?: string
- last_user_stats?: Record
- last_server_stats?: Record
+ enabled: boolean;
+ times: string[];
+ next_run?: string;
+ is_running: boolean;
+ last_run_started_at?: string;
+ last_run_finished_at?: string;
+ last_run_success?: boolean;
+ last_run_reason?: string;
+ last_run_error?: string;
+ last_user_stats?: Record;
+ last_server_stats?: Record;
}
export interface AutoSyncRunResponse {
- started: boolean
- success?: boolean
- error?: string
- user_stats?: Record
- server_stats?: Record
- reason?: string
+ started: boolean;
+ success?: boolean;
+ error?: string;
+ user_stats?: Record;
+ server_stats?: Record;
+ reason?: string;
}
// Sync
export interface SyncResponse {
- success: boolean
- message?: string
- data?: Record
+ success: boolean;
+ message?: string;
+ data?: Record;
}
// ============ API ============
@@ -229,158 +229,176 @@ export interface SyncResponse {
export const adminRemnawaveApi = {
// Status & Connection
getStatus: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/status')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/status');
+ return response.data;
},
// System Statistics
getSystemStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/system')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/system');
+ return response.data;
},
// Nodes
getNodes: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/nodes')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/nodes');
+ return response.data;
},
getNodesOverview: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/nodes/overview')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/nodes/overview');
+ return response.data;
},
getNodesRealtime: async (): Promise[]> => {
- const response = await apiClient.get('/cabinet/admin/remnawave/nodes/realtime')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/nodes/realtime');
+ return response.data;
},
getNode: async (uuid: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/remnawave/nodes/${uuid}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/remnawave/nodes/${uuid}`);
+ return response.data;
},
getNodeStatistics: async (uuid: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/remnawave/nodes/${uuid}/statistics`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/remnawave/nodes/${uuid}/statistics`);
+ return response.data;
},
- nodeAction: async (uuid: string, action: 'enable' | 'disable' | 'restart'): Promise => {
- const response = await apiClient.post(`/cabinet/admin/remnawave/nodes/${uuid}/action`, { action })
- return response.data
+ nodeAction: async (
+ uuid: string,
+ action: 'enable' | 'disable' | 'restart',
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/remnawave/nodes/${uuid}/action`, {
+ action,
+ });
+ return response.data;
},
restartAllNodes: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/nodes/restart-all')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/nodes/restart-all');
+ return response.data;
},
// Squads
getSquads: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/squads')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/squads');
+ return response.data;
},
getSquad: async (uuid: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/remnawave/squads/${uuid}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/remnawave/squads/${uuid}`);
+ return response.data;
},
- createSquad: async (data: { name: string; inbound_uuids?: string[] }): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/squads', data)
- return response.data
+ createSquad: async (data: {
+ name: string;
+ inbound_uuids?: string[];
+ }): Promise => {
+ const response = await apiClient.post('/cabinet/admin/remnawave/squads', data);
+ return response.data;
},
- updateSquad: async (uuid: string, data: { name?: string; inbound_uuids?: string[] }): Promise => {
- const response = await apiClient.patch(`/cabinet/admin/remnawave/squads/${uuid}`, data)
- return response.data
+ updateSquad: async (
+ uuid: string,
+ data: { name?: string; inbound_uuids?: string[] },
+ ): Promise => {
+ const response = await apiClient.patch(`/cabinet/admin/remnawave/squads/${uuid}`, data);
+ return response.data;
},
deleteSquad: async (uuid: string): Promise => {
- const response = await apiClient.delete(`/cabinet/admin/remnawave/squads/${uuid}`)
- return response.data
+ const response = await apiClient.delete(`/cabinet/admin/remnawave/squads/${uuid}`);
+ return response.data;
},
- squadAction: async (uuid: string, data: {
- action: 'add_all_users' | 'remove_all_users' | 'delete' | 'rename' | 'update_inbounds'
- name?: string
- inbound_uuids?: string[]
- }): Promise => {
- const response = await apiClient.post(`/cabinet/admin/remnawave/squads/${uuid}/action`, data)
- return response.data
+ squadAction: async (
+ uuid: string,
+ data: {
+ action: 'add_all_users' | 'remove_all_users' | 'delete' | 'rename' | 'update_inbounds';
+ name?: string;
+ inbound_uuids?: string[];
+ },
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/remnawave/squads/${uuid}/action`, data);
+ return response.data;
},
// Migration
getMigrationPreview: async (uuid: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/remnawave/squads/${uuid}/migration-preview`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/remnawave/squads/${uuid}/migration-preview`,
+ );
+ return response.data;
},
migrateSquad: async (sourceUuid: string, targetUuid: string): Promise => {
const response = await apiClient.post('/cabinet/admin/remnawave/squads/migrate', {
source_uuid: sourceUuid,
target_uuid: targetUuid,
- })
- return response.data
+ });
+ return response.data;
},
// Inbounds
getInbounds: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/inbounds')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/inbounds');
+ return response.data;
},
// Auto Sync
getAutoSyncStatus: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/sync/auto/status')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/sync/auto/status');
+ return response.data;
},
toggleAutoSync: async (enabled: boolean): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/auto/toggle', { enabled })
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/auto/toggle', { enabled });
+ return response.data;
},
runAutoSync: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/auto/run')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/auto/run');
+ return response.data;
},
// Manual Sync
- syncFromPanel: async (mode: 'all' | 'new_only' | 'update_only' = 'all'): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/from-panel', { mode })
- return response.data
+ syncFromPanel: async (
+ mode: 'all' | 'new_only' | 'update_only' = 'all',
+ ): Promise => {
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/from-panel', { mode });
+ return response.data;
},
syncToPanel: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/to-panel')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/to-panel');
+ return response.data;
},
syncServers: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/servers')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/servers');
+ return response.data;
},
validateSubscriptions: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/validate')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/validate');
+ return response.data;
},
cleanupSubscriptions: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/cleanup')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/cleanup');
+ return response.data;
},
syncSubscriptionStatuses: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/statuses')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/statuses');
+ return response.data;
},
getSyncRecommendations: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/sync/recommendations')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/sync/recommendations');
+ return response.data;
},
-}
+};
-export default adminRemnawaveApi
+export default adminRemnawaveApi;
diff --git a/src/api/adminSettings.ts b/src/api/adminSettings.ts
index 391dca3..84d3932 100644
--- a/src/api/adminSettings.ts
+++ b/src/api/adminSettings.ts
@@ -1,73 +1,79 @@
-import apiClient from './client'
+import apiClient from './client';
export interface SettingCategoryRef {
- key: string
- label: string
+ key: string;
+ label: string;
}
export interface SettingCategorySummary {
- key: string
- label: string
- description: string
- items: number
+ key: string;
+ label: string;
+ description: string;
+ items: number;
}
export interface SettingChoice {
- value: unknown
- label: string
- description?: string | null
+ value: unknown;
+ label: string;
+ description?: string | null;
}
export interface SettingHint {
- description: string
- format: string
- example: string
- warning: string
+ description: string;
+ format: string;
+ example: string;
+ warning: string;
}
export interface SettingDefinition {
- key: string
- name: string
- category: SettingCategoryRef
- type: string
- is_optional: boolean
- current: unknown
- original: unknown
- has_override: boolean
- read_only: boolean
- choices: SettingChoice[]
- hint?: SettingHint | null
+ key: string;
+ name: string;
+ category: SettingCategoryRef;
+ type: string;
+ is_optional: boolean;
+ current: unknown;
+ original: unknown;
+ has_override: boolean;
+ read_only: boolean;
+ choices: SettingChoice[];
+ hint?: SettingHint | null;
}
export const adminSettingsApi = {
// Get list of setting categories
getCategories: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/settings/categories')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/settings/categories',
+ );
+ return response.data;
},
// Get all settings or settings for a specific category
getSettings: async (categoryKey?: string): Promise => {
- const params = categoryKey ? { category_key: categoryKey } : {}
- const response = await apiClient.get('/cabinet/admin/settings', { params })
- return response.data
+ const params = categoryKey ? { category_key: categoryKey } : {};
+ const response = await apiClient.get('/cabinet/admin/settings', {
+ params,
+ });
+ return response.data;
},
// Get a specific setting by key
getSetting: async (key: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/settings/${key}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/settings/${key}`);
+ return response.data;
},
// Update a setting value
updateSetting: async (key: string, value: unknown): Promise => {
- const response = await apiClient.put(`/cabinet/admin/settings/${key}`, { value })
- return response.data
+ const response = await apiClient.put(`/cabinet/admin/settings/${key}`, {
+ value,
+ });
+ return response.data;
},
// Reset a setting to default
resetSetting: async (key: string): Promise => {
- const response = await apiClient.delete(`/cabinet/admin/settings/${key}`)
- return response.data
+ const response = await apiClient.delete(`/cabinet/admin/settings/${key}`);
+ return response.data;
},
-}
+};
diff --git a/src/api/adminUsers.ts b/src/api/adminUsers.ts
index b0e48be..88e204d 100644
--- a/src/api/adminUsers.ts
+++ b/src/api/adminUsers.ts
@@ -1,418 +1,478 @@
-import apiClient from './client'
+import apiClient from './client';
// ============ Types ============
export interface UserSubscriptionInfo {
- id: number
- status: string
- is_trial: boolean
- start_date: string | null
- end_date: string | null
- traffic_limit_gb: number
- traffic_used_gb: number
- device_limit: number
- tariff_id: number | null
- tariff_name: string | null
- autopay_enabled: boolean
- is_active: boolean
- days_remaining: number
+ id: number;
+ status: string;
+ is_trial: boolean;
+ start_date: string | null;
+ end_date: string | null;
+ traffic_limit_gb: number;
+ traffic_used_gb: number;
+ device_limit: number;
+ tariff_id: number | null;
+ tariff_name: string | null;
+ autopay_enabled: boolean;
+ is_active: boolean;
+ days_remaining: number;
}
export interface UserPromoGroupInfo {
- id: number
- name: string
- is_default: boolean
+ id: number;
+ name: string;
+ is_default: boolean;
}
export interface UserListItem {
- id: number
- telegram_id: number
- username: string | null
- first_name: string | null
- last_name: string | null
- full_name: string
- status: string
- balance_kopeks: number
- balance_rubles: number
- created_at: string
- last_activity: string | null
- has_subscription: boolean
- subscription_status: string | null
- subscription_is_trial: boolean
- subscription_end_date: string | null
- promo_group_id: number | null
- promo_group_name: string | null
- total_spent_kopeks: number
- purchase_count: number
- has_restrictions: boolean
- restriction_topup: boolean
- restriction_subscription: boolean
+ id: number;
+ telegram_id: number;
+ username: string | null;
+ first_name: string | null;
+ last_name: string | null;
+ full_name: string;
+ status: string;
+ balance_kopeks: number;
+ balance_rubles: number;
+ created_at: string;
+ last_activity: string | null;
+ has_subscription: boolean;
+ subscription_status: string | null;
+ subscription_is_trial: boolean;
+ subscription_end_date: string | null;
+ promo_group_id: number | null;
+ promo_group_name: string | null;
+ total_spent_kopeks: number;
+ purchase_count: number;
+ has_restrictions: boolean;
+ restriction_topup: boolean;
+ restriction_subscription: boolean;
}
export interface UsersListResponse {
- users: UserListItem[]
- total: number
- offset: number
- limit: number
+ users: UserListItem[];
+ total: number;
+ offset: number;
+ limit: number;
}
export interface UserTransactionItem {
- id: number
- type: string
- amount_kopeks: number
- amount_rubles: number
- description: string | null
- payment_method: string | null
- is_completed: boolean
- created_at: string
+ id: number;
+ type: string;
+ amount_kopeks: number;
+ amount_rubles: number;
+ description: string | null;
+ payment_method: string | null;
+ is_completed: boolean;
+ created_at: string;
}
export interface UserReferralInfo {
- referral_code: string
- referrals_count: number
- total_earnings_kopeks: number
- commission_percent: number | null
- referred_by_id: number | null
- referred_by_username: string | null
+ referral_code: string;
+ referrals_count: number;
+ total_earnings_kopeks: number;
+ commission_percent: number | null;
+ referred_by_id: number | null;
+ referred_by_username: string | null;
}
export interface UserDetailResponse {
- id: number
- telegram_id: number
- username: string | null
- first_name: string | null
- last_name: string | null
- full_name: string
- status: string
- language: string
- balance_kopeks: number
- balance_rubles: number
- email: string | null
- email_verified: boolean
- created_at: string
- updated_at: string | null
- last_activity: string | null
- cabinet_last_login: string | null
- subscription: UserSubscriptionInfo | null
- promo_group: UserPromoGroupInfo | null
- referral: UserReferralInfo
- total_spent_kopeks: number
- purchase_count: number
- used_promocodes: number
- has_had_paid_subscription: boolean
- lifetime_used_traffic_bytes: number
- restriction_topup: boolean
- restriction_subscription: boolean
- restriction_reason: string | null
- promo_offer_discount_percent: number
- promo_offer_discount_source: string | null
- promo_offer_discount_expires_at: string | null
- recent_transactions: UserTransactionItem[]
- remnawave_uuid: string | null
+ id: number;
+ telegram_id: number;
+ username: string | null;
+ first_name: string | null;
+ last_name: string | null;
+ full_name: string;
+ status: string;
+ language: string;
+ balance_kopeks: number;
+ balance_rubles: number;
+ email: string | null;
+ email_verified: boolean;
+ created_at: string;
+ updated_at: string | null;
+ last_activity: string | null;
+ cabinet_last_login: string | null;
+ subscription: UserSubscriptionInfo | null;
+ promo_group: UserPromoGroupInfo | null;
+ referral: UserReferralInfo;
+ total_spent_kopeks: number;
+ purchase_count: number;
+ used_promocodes: number;
+ has_had_paid_subscription: boolean;
+ lifetime_used_traffic_bytes: number;
+ restriction_topup: boolean;
+ restriction_subscription: boolean;
+ restriction_reason: string | null;
+ promo_offer_discount_percent: number;
+ promo_offer_discount_source: string | null;
+ promo_offer_discount_expires_at: string | null;
+ recent_transactions: UserTransactionItem[];
+ remnawave_uuid: string | null;
}
export interface UsersStatsResponse {
- total_users: number
- active_users: number
- blocked_users: number
- deleted_users: number
- new_today: number
- new_week: number
- new_month: number
- users_with_subscription: number
- users_with_active_subscription: number
- users_with_trial: number
- users_with_expired_subscription: number
- total_balance_kopeks: number
- total_balance_rubles: number
- avg_balance_kopeks: number
- active_today: number
- active_week: number
- active_month: number
+ total_users: number;
+ active_users: number;
+ blocked_users: number;
+ deleted_users: number;
+ new_today: number;
+ new_week: number;
+ new_month: number;
+ users_with_subscription: number;
+ users_with_active_subscription: number;
+ users_with_trial: number;
+ users_with_expired_subscription: number;
+ total_balance_kopeks: number;
+ total_balance_rubles: number;
+ avg_balance_kopeks: number;
+ active_today: number;
+ active_week: number;
+ active_month: number;
}
// Available tariffs
export interface PeriodPriceInfo {
- days: number
- price_kopeks: number
- price_rubles: number
+ days: number;
+ price_kopeks: number;
+ price_rubles: number;
}
export interface UserAvailableTariff {
- id: number
- name: string
- description: string | null
- is_active: boolean
- is_trial_available: boolean
- traffic_limit_gb: number
- device_limit: number
- tier_level: number
- display_order: number
- period_prices: PeriodPriceInfo[]
- is_daily: boolean
- daily_price_kopeks: number
- custom_days_enabled: boolean
- price_per_day_kopeks: number
- min_days: number
- max_days: number
- is_available: boolean
- requires_promo_group: boolean
+ id: number;
+ name: string;
+ description: string | null;
+ is_active: boolean;
+ is_trial_available: boolean;
+ traffic_limit_gb: number;
+ device_limit: number;
+ tier_level: number;
+ display_order: number;
+ period_prices: PeriodPriceInfo[];
+ is_daily: boolean;
+ daily_price_kopeks: number;
+ custom_days_enabled: boolean;
+ price_per_day_kopeks: number;
+ min_days: number;
+ max_days: number;
+ is_available: boolean;
+ requires_promo_group: boolean;
}
export interface UserAvailableTariffsResponse {
- user_id: number
- promo_group_id: number | null
- promo_group_name: string | null
- tariffs: UserAvailableTariff[]
- total: number
- current_tariff_id: number | null
- current_tariff_name: string | null
+ user_id: number;
+ promo_group_id: number | null;
+ promo_group_name: string | null;
+ tariffs: UserAvailableTariff[];
+ total: number;
+ current_tariff_id: number | null;
+ current_tariff_name: string | null;
}
// Sync types
export interface PanelUserInfo {
- uuid: string | null
- short_uuid: string | null
- username: string | null
- status: string | null
- expire_at: string | null
- traffic_limit_gb: number
- traffic_used_gb: number
- device_limit: number
- subscription_url: string | null
- active_squads: string[]
+ uuid: string | null;
+ short_uuid: string | null;
+ username: string | null;
+ status: string | null;
+ expire_at: string | null;
+ traffic_limit_gb: number;
+ traffic_used_gb: number;
+ device_limit: number;
+ subscription_url: string | null;
+ active_squads: string[];
}
export interface SyncFromPanelResponse {
- success: boolean
- message: string
- panel_user: PanelUserInfo | null
- changes: Record
- errors: string[]
+ success: boolean;
+ message: string;
+ panel_user: PanelUserInfo | null;
+ changes: Record;
+ errors: string[];
}
export interface SyncToPanelResponse {
- success: boolean
- message: string
- action: string
- panel_uuid: string | null
- changes: Record
- errors: string[]
+ success: boolean;
+ message: string;
+ action: string;
+ panel_uuid: string | null;
+ changes: Record;
+ errors: string[];
}
export interface PanelSyncStatusResponse {
- user_id: number
- telegram_id: number
- remnawave_uuid: string | null
- last_sync: string | null
- bot_subscription_status: string | null
- bot_subscription_end_date: string | null
- bot_traffic_limit_gb: number
- bot_traffic_used_gb: number
- bot_device_limit: number
- bot_squads: string[]
- panel_found: boolean
- panel_status: string | null
- panel_expire_at: string | null
- panel_traffic_limit_gb: number
- panel_traffic_used_gb: number
- panel_device_limit: number
- panel_squads: string[]
- has_differences: boolean
- differences: string[]
+ user_id: number;
+ telegram_id: number;
+ remnawave_uuid: string | null;
+ last_sync: string | null;
+ bot_subscription_status: string | null;
+ bot_subscription_end_date: string | null;
+ bot_traffic_limit_gb: number;
+ bot_traffic_used_gb: number;
+ bot_device_limit: number;
+ bot_squads: string[];
+ panel_found: boolean;
+ panel_status: string | null;
+ panel_expire_at: string | null;
+ panel_traffic_limit_gb: number;
+ panel_traffic_used_gb: number;
+ panel_device_limit: number;
+ panel_squads: string[];
+ has_differences: boolean;
+ differences: string[];
}
// Update types
export interface UpdateBalanceRequest {
- amount_kopeks: number
- description?: string
- create_transaction?: boolean
+ amount_kopeks: number;
+ description?: string;
+ create_transaction?: boolean;
}
export interface UpdateBalanceResponse {
- success: boolean
- old_balance_kopeks: number
- new_balance_kopeks: number
- message: string
+ success: boolean;
+ old_balance_kopeks: number;
+ new_balance_kopeks: number;
+ message: string;
}
export interface UpdateSubscriptionRequest {
- action: 'extend' | 'set_end_date' | 'change_tariff' | 'set_traffic' | 'toggle_autopay' | 'cancel' | 'activate' | 'create'
- days?: number
- end_date?: string
- tariff_id?: number
- traffic_limit_gb?: number
- traffic_used_gb?: number
- autopay_enabled?: boolean
- is_trial?: boolean
- device_limit?: number
+ action:
+ | 'extend'
+ | 'set_end_date'
+ | 'change_tariff'
+ | 'set_traffic'
+ | 'toggle_autopay'
+ | 'cancel'
+ | 'activate'
+ | 'create';
+ days?: number;
+ end_date?: string;
+ tariff_id?: number;
+ traffic_limit_gb?: number;
+ traffic_used_gb?: number;
+ autopay_enabled?: boolean;
+ is_trial?: boolean;
+ device_limit?: number;
}
export interface UpdateSubscriptionResponse {
- success: boolean
- message: string
- subscription: UserSubscriptionInfo | null
+ success: boolean;
+ message: string;
+ subscription: UserSubscriptionInfo | null;
}
export interface UpdateUserStatusResponse {
- success: boolean
- old_status: string
- new_status: string
- message: string
+ success: boolean;
+ old_status: string;
+ new_status: string;
+ message: string;
}
export interface UpdateRestrictionsRequest {
- restriction_topup?: boolean
- restriction_subscription?: boolean
- restriction_reason?: string
+ restriction_topup?: boolean;
+ restriction_subscription?: boolean;
+ restriction_reason?: string;
}
export interface UpdateRestrictionsResponse {
- success: boolean
- restriction_topup: boolean
- restriction_subscription: boolean
- restriction_reason: string | null
- message: string
+ success: boolean;
+ restriction_topup: boolean;
+ restriction_subscription: boolean;
+ restriction_reason: string | null;
+ message: string;
}
export interface SyncFromPanelRequest {
- update_subscription?: boolean
- update_traffic?: boolean
- create_if_missing?: boolean
+ update_subscription?: boolean;
+ update_traffic?: boolean;
+ create_if_missing?: boolean;
}
export interface SyncToPanelRequest {
- create_if_missing?: boolean
- update_status?: boolean
- update_traffic_limit?: boolean
- update_expire_date?: boolean
- update_squads?: boolean
+ create_if_missing?: boolean;
+ update_status?: boolean;
+ update_traffic_limit?: boolean;
+ update_expire_date?: boolean;
+ update_squads?: boolean;
}
// ============ API ============
export const adminUsersApi = {
// List users
- getUsers: async (params: {
- offset?: number
- limit?: number
- search?: string
- status?: 'active' | 'blocked' | 'deleted'
- sort_by?: 'created_at' | 'balance' | 'traffic' | 'last_activity' | 'total_spent' | 'purchase_count'
- } = {}): Promise => {
- const response = await apiClient.get('/cabinet/admin/users', { params })
- return response.data
+ getUsers: async (
+ params: {
+ offset?: number;
+ limit?: number;
+ search?: string;
+ status?: 'active' | 'blocked' | 'deleted';
+ sort_by?:
+ | 'created_at'
+ | 'balance'
+ | 'traffic'
+ | 'last_activity'
+ | 'total_spent'
+ | 'purchase_count';
+ } = {},
+ ): Promise => {
+ const response = await apiClient.get('/cabinet/admin/users', { params });
+ return response.data;
},
// Get users stats
getStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/users/stats')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/users/stats');
+ return response.data;
},
// Get user detail
getUser: async (userId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/users/${userId}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/users/${userId}`);
+ return response.data;
},
// Get user by telegram ID
getUserByTelegram: async (telegramId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/users/by-telegram/${telegramId}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/users/by-telegram/${telegramId}`);
+ return response.data;
},
// Get available tariffs for user
- getAvailableTariffs: async (userId: number, includeInactive = false): Promise => {
+ getAvailableTariffs: async (
+ userId: number,
+ includeInactive = false,
+ ): Promise => {
const response = await apiClient.get(`/cabinet/admin/users/${userId}/available-tariffs`, {
- params: { include_inactive: includeInactive }
- })
- return response.data
+ params: { include_inactive: includeInactive },
+ });
+ return response.data;
},
// Update balance
- updateBalance: async (userId: number, data: UpdateBalanceRequest): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/balance`, data)
- return response.data
+ updateBalance: async (
+ userId: number,
+ data: UpdateBalanceRequest,
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/balance`, data);
+ return response.data;
},
// Update subscription
- updateSubscription: async (userId: number, data: UpdateSubscriptionRequest): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/subscription`, data)
- return response.data
+ updateSubscription: async (
+ userId: number,
+ data: UpdateSubscriptionRequest,
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/subscription`, data);
+ return response.data;
},
// Update status
- updateStatus: async (userId: number, status: 'active' | 'blocked' | 'deleted', reason?: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/status`, { status, reason })
- return response.data
+ updateStatus: async (
+ userId: number,
+ status: 'active' | 'blocked' | 'deleted',
+ reason?: string,
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/status`, {
+ status,
+ reason,
+ });
+ return response.data;
},
// Block user
blockUser: async (userId: number, reason?: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/block`, null, { params: { reason } })
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/block`, null, {
+ params: { reason },
+ });
+ return response.data;
},
// Unblock user
unblockUser: async (userId: number): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/unblock`)
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/unblock`);
+ return response.data;
},
// Update restrictions
- updateRestrictions: async (userId: number, data: UpdateRestrictionsRequest): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/restrictions`, data)
- return response.data
+ updateRestrictions: async (
+ userId: number,
+ data: UpdateRestrictionsRequest,
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/restrictions`, data);
+ return response.data;
},
// Update promo group
- updatePromoGroup: async (userId: number, promoGroupId: number | null): Promise<{ success: boolean; message: string }> => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/promo-group`, { promo_group_id: promoGroupId })
- return response.data
+ updatePromoGroup: async (
+ userId: number,
+ promoGroupId: number | null,
+ ): Promise<{ success: boolean; message: string }> => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/promo-group`, {
+ promo_group_id: promoGroupId,
+ });
+ return response.data;
},
// Delete user
- deleteUser: async (userId: number, softDelete = true, reason?: string): Promise<{ success: boolean; message: string }> => {
+ deleteUser: async (
+ userId: number,
+ softDelete = true,
+ reason?: string,
+ ): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.delete(`/cabinet/admin/users/${userId}`, {
- data: { soft_delete: softDelete, reason }
- })
- return response.data
+ data: { soft_delete: softDelete, reason },
+ });
+ return response.data;
},
// Get referrals
getReferrals: async (userId: number, offset = 0, limit = 50): Promise => {
const response = await apiClient.get(`/cabinet/admin/users/${userId}/referrals`, {
- params: { offset, limit }
- })
- return response.data
+ params: { offset, limit },
+ });
+ return response.data;
},
// Get transactions
- getTransactions: async (userId: number, params: {
- offset?: number
- limit?: number
- transaction_type?: string
- } = {}): Promise<{ transactions: UserTransactionItem[]; total: number; offset: number; limit: number }> => {
- const response = await apiClient.get(`/cabinet/admin/users/${userId}/transactions`, { params })
- return response.data
+ getTransactions: async (
+ userId: number,
+ params: {
+ offset?: number;
+ limit?: number;
+ transaction_type?: string;
+ } = {},
+ ): Promise<{
+ transactions: UserTransactionItem[];
+ total: number;
+ offset: number;
+ limit: number;
+ }> => {
+ const response = await apiClient.get(`/cabinet/admin/users/${userId}/transactions`, { params });
+ return response.data;
},
// Sync status
getSyncStatus: async (userId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`);
+ return response.data;
},
// Sync from panel
- syncFromPanel: async (userId: number, data: SyncFromPanelRequest = {}): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/from-panel`, data)
- return response.data
+ syncFromPanel: async (
+ userId: number,
+ data: SyncFromPanelRequest = {},
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/from-panel`, data);
+ return response.data;
},
// Sync to panel
- syncToPanel: async (userId: number, data: SyncToPanelRequest = {}): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/to-panel`, data)
- return response.data
+ syncToPanel: async (
+ userId: number,
+ data: SyncToPanelRequest = {},
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/to-panel`, data);
+ return response.data;
},
-}
+};
diff --git a/src/api/auth.ts b/src/api/auth.ts
index a5697e2..d7104e9 100644
--- a/src/api/auth.ts
+++ b/src/api/auth.ts
@@ -1,27 +1,27 @@
-import apiClient from './client'
-import type { AuthResponse, RegisterResponse, TokenResponse, User } from '../types'
+import apiClient from './client';
+import type { AuthResponse, RegisterResponse, TokenResponse, User } from '../types';
export const authApi = {
// Telegram WebApp authentication
loginTelegram: async (initData: string): Promise => {
const response = await apiClient.post('/cabinet/auth/telegram', {
init_data: initData,
- })
- return response.data
+ });
+ return response.data;
},
// Telegram Login Widget authentication
loginTelegramWidget: async (data: {
- id: number
- first_name: string
- last_name?: string
- username?: string
- photo_url?: string
- auth_date: number
- hash: string
+ id: number;
+ first_name: string;
+ last_name?: string;
+ username?: string;
+ photo_url?: string;
+ auth_date: number;
+ hash: string;
}): Promise => {
- const response = await apiClient.post('/cabinet/auth/telegram/widget', data)
- return response.data
+ const response = await apiClient.post('/cabinet/auth/telegram/widget', data);
+ return response.data;
},
// Email login
@@ -29,63 +29,69 @@ export const authApi = {
const response = await apiClient.post('/cabinet/auth/email/login', {
email,
password,
- })
- return response.data
+ });
+ return response.data;
},
// Register email (link to existing Telegram account)
- registerEmail: async (email: string, password: string): Promise<{ message: string; email: string }> => {
+ registerEmail: async (
+ email: string,
+ password: string,
+ ): Promise<{ message: string; email: string }> => {
const response = await apiClient.post('/cabinet/auth/email/register', {
email,
password,
- })
- return response.data
+ });
+ return response.data;
},
// Register standalone email account (no Telegram required)
// Returns message - user must verify email before login
registerEmailStandalone: async (data: {
- email: string
- password: string
- first_name?: string
- language?: string
- referral_code?: string
+ email: string;
+ password: string;
+ first_name?: string;
+ language?: string;
+ referral_code?: string;
}): Promise => {
- const response = await apiClient.post('/cabinet/auth/email/register/standalone', data)
- return response.data
+ const response = await apiClient.post(
+ '/cabinet/auth/email/register/standalone',
+ data,
+ );
+ return response.data;
},
// Verify email and get auth tokens
verifyEmail: async (token: string): Promise => {
- const response = await apiClient.post('/cabinet/auth/email/verify', { token })
- return response.data
+ const response = await apiClient.post('/cabinet/auth/email/verify', { token });
+ return response.data;
},
// Resend verification email
resendVerification: async (): Promise<{ message: string }> => {
- const response = await apiClient.post('/cabinet/auth/email/resend')
- return response.data
+ const response = await apiClient.post('/cabinet/auth/email/resend');
+ return response.data;
},
// Refresh token
refreshToken: async (refreshToken: string): Promise => {
const response = await apiClient.post('/cabinet/auth/refresh', {
refresh_token: refreshToken,
- })
- return response.data
+ });
+ return response.data;
},
// Logout
logout: async (refreshToken: string): Promise => {
await apiClient.post('/cabinet/auth/logout', {
refresh_token: refreshToken,
- })
+ });
},
// Forgot password
forgotPassword: async (email: string): Promise<{ message: string }> => {
- const response = await apiClient.post('/cabinet/auth/password/forgot', { email })
- return response.data
+ const response = await apiClient.post('/cabinet/auth/password/forgot', { email });
+ return response.data;
},
// Reset password
@@ -93,13 +99,13 @@ export const authApi = {
const response = await apiClient.post('/cabinet/auth/password/reset', {
token,
password,
- })
- return response.data
+ });
+ return response.data;
},
// Get current user
getMe: async (): Promise => {
- const response = await apiClient.get('/cabinet/auth/me')
- return response.data
+ const response = await apiClient.get('/cabinet/auth/me');
+ return response.data;
},
-}
+};
diff --git a/src/api/balance.ts b/src/api/balance.ts
index d2aeef4..1772485 100644
--- a/src/api/balance.ts
+++ b/src/api/balance.ts
@@ -1,100 +1,124 @@
-import apiClient from './client'
-import type { Balance, Transaction, PaymentMethod, PaginatedResponse, PendingPayment, ManualCheckResponse } from '../types'
+import apiClient from './client';
+import type {
+ Balance,
+ Transaction,
+ PaymentMethod,
+ PaginatedResponse,
+ PendingPayment,
+ ManualCheckResponse,
+} from '../types';
export const balanceApi = {
// Get current balance
getBalance: async (): Promise => {
- const response = await apiClient.get('/cabinet/balance')
- return response.data
+ const response = await apiClient.get('/cabinet/balance');
+ return response.data;
},
// Get transaction history
getTransactions: async (params?: {
- page?: number
- per_page?: number
- type?: string
+ page?: number;
+ per_page?: number;
+ type?: string;
}): Promise> => {
- const response = await apiClient.get>('/cabinet/balance/transactions', {
- params,
- })
- return response.data
+ const response = await apiClient.get