diff --git a/package-lock.json b/package-lock.json index 5842e53..ee78840 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,6 @@ "react-i18next": "^16.5.4", "react-router": "^7.13.0", "react-twemoji": "^0.7.2", - "reactjs-tiptap-editor": "^1.0.19", "recharts": "^3.7.0", "sigma": "^3.0.2", "simplex-noise": "^4.0.3", @@ -101,19 +100,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@antfu/install-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", - "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", - "license": "MIT", - "dependencies": { - "package-manager-detector": "^1.3.0", - "tinyexec": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -405,51 +391,6 @@ "node": ">=6.9.0" } }, - "node_modules/@braintree/sanitize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz", - "integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==", - "license": "MIT" - }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", - "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.1.2", - "@chevrotain/types": "11.1.2", - "lodash-es": "4.17.23" - } - }, - "node_modules/@chevrotain/gast": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz", - "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.1.2", - "lodash-es": "4.17.23" - } - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", - "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/types": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", - "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.2.tgz", - "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==", - "license": "Apache-2.0" - }, "node_modules/@dnd-kit/accessibility": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", @@ -1102,868 +1043,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@excalidraw/excalidraw": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.18.0.tgz", - "integrity": "sha512-QkIiS+5qdy8lmDWTKsuy0sK/fen/LRDtbhm2lc2xcFcqhv2/zdg95bYnl+wnwwXGHo7kEmP65BSiMHE7PJ3Zpw==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "6.0.2", - "@excalidraw/laser-pointer": "1.3.1", - "@excalidraw/mermaid-to-excalidraw": "1.1.2", - "@excalidraw/random-username": "1.1.0", - "@radix-ui/react-popover": "1.1.6", - "@radix-ui/react-tabs": "1.0.2", - "browser-fs-access": "0.29.1", - "canvas-roundrect-polyfill": "0.0.1", - "clsx": "1.1.1", - "cross-env": "7.0.3", - "es6-promise-pool": "2.5.0", - "fractional-indexing": "3.2.0", - "fuzzy": "0.1.3", - "image-blob-reduce": "3.0.1", - "jotai": "2.11.0", - "jotai-scope": "0.7.2", - "lodash.debounce": "4.0.8", - "lodash.throttle": "4.1.1", - "nanoid": "3.3.3", - "open-color": "1.9.1", - "pako": "2.0.3", - "perfect-freehand": "1.2.0", - "pica": "7.1.1", - "png-chunk-text": "1.0.0", - "png-chunks-encode": "1.0.0", - "png-chunks-extract": "1.0.0", - "points-on-curve": "1.0.1", - "pwacompat": "2.0.17", - "roughjs": "4.6.4", - "sass": "1.51.0", - "tunnel-rat": "0.1.2" - }, - "peerDependencies": { - "react": "^17.0.2 || ^18.2.0 || ^19.0.0", - "react-dom": "^17.0.2 || ^18.2.0 || ^19.0.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", - "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", - "license": "MIT" - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-arrow": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", - "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", - "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", - "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", - "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz", - "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-popover": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz", - "integrity": "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.5", - "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.2", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.2", - "@radix-ui/react-portal": "1.1.4", - "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-slot": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.1.0", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-popper": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", - "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.2", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-portal": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", - "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-presence": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", - "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-primitive": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", - "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", - "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.2.tgz", - "integrity": "sha512-gOUwh+HbjCuL0UCo8kZ+kdUEG8QtpdO4sMQduJ34ZEz0r4922g9REOBM+vIsfwtGxSug4Yb1msJMJYN2Bk8TpQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-direction": "1.0.0", - "@radix-ui/react-id": "1.0.0", - "@radix-ui/react-presence": "1.0.0", - "@radix-ui/react-primitive": "1.0.1", - "@radix-ui/react-roving-focus": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", - "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz", - "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-direction": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz", - "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", - "integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-id/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", - "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz", - "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-use-layout-effect": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence/node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", - "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", - "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz", - "integrity": "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.1" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", - "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", - "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.2.tgz", - "integrity": "sha512-HLK+CqD/8pN6GfJm3U+cqpqhSKYAWiOJDe+A+8MfxBnOue39QEeMa43csUn2CXCHQT0/mewh1LrrG4tfkM9DMA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-collection": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-direction": "1.0.0", - "@radix-ui/react-id": "1.0.0", - "@radix-ui/react-primitive": "1.0.1", - "@radix-ui/react-use-callback-ref": "1.0.0", - "@radix-ui/react-use-controllable-state": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.1.tgz", - "integrity": "sha512-uuiFbs+YCKjn3X1DTSx9G7BHApu4GHbi3kgiwsnFUbOKCrwejAJv4eE4Vc8C0Oaxt9T0aV4ox0WCOdx+39Xo+g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-primitive": "1.0.1", - "@radix-ui/react-slot": "1.0.1" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", - "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", - "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", - "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz", - "integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-controllable-state/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", - "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", - "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", - "license": "MIT" - }, - "node_modules/@excalidraw/excalidraw/node_modules/clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/immutable": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz", - "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==", - "license": "MIT" - }, - "node_modules/@excalidraw/excalidraw/node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/sass": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.51.0.tgz", - "integrity": "sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==", - "license": "MIT", - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@excalidraw/laser-pointer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@excalidraw/laser-pointer/-/laser-pointer-1.3.1.tgz", - "integrity": "sha512-psA1z1N2qeAfsORdXc9JmD2y4CmDwmuMRxnNdJHZexIcPwaNEyIpNcelw+QkL9rz9tosaN9krXuKaRqYpRAR6g==", - "license": "MIT" - }, - "node_modules/@excalidraw/markdown-to-text": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@excalidraw/markdown-to-text/-/markdown-to-text-0.1.2.tgz", - "integrity": "sha512-1nDXBNAojfi3oSFwJswKREkFm5wrSjqay81QlyRv2pkITG/XYB5v+oChENVBQLcxQwX4IUATWvXM5BcaNhPiIg==", - "license": "MIT" - }, - "node_modules/@excalidraw/mermaid-to-excalidraw": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@excalidraw/mermaid-to-excalidraw/-/mermaid-to-excalidraw-1.1.2.tgz", - "integrity": "sha512-hAFv/TTIsOdoy0dL5v+oBd297SQ+Z88gZ5u99fCIFuEMHfQuPgLhU/ztKhFSTs7fISwVo6fizny/5oQRR3d4tQ==", - "license": "MIT", - "dependencies": { - "@excalidraw/markdown-to-text": "0.1.2", - "mermaid": "10.9.3", - "nanoid": "4.0.2" - } - }, - "node_modules/@excalidraw/mermaid-to-excalidraw/node_modules/dagre-d3-es": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", - "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", - "license": "MIT", - "dependencies": { - "d3": "^7.8.2", - "lodash-es": "^4.17.21" - } - }, - "node_modules/@excalidraw/mermaid-to-excalidraw/node_modules/dompurify": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", - "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==", - "license": "(MPL-2.0 OR Apache-2.0)" - }, - "node_modules/@excalidraw/mermaid-to-excalidraw/node_modules/mermaid": { - "version": "10.9.3", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.3.tgz", - "integrity": "sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^6.0.1", - "@types/d3-scale": "^4.0.3", - "@types/d3-scale-chromatic": "^3.0.0", - "cytoscape": "^3.28.1", - "cytoscape-cose-bilkent": "^4.1.0", - "d3": "^7.4.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.10", - "dayjs": "^1.11.7", - "dompurify": "^3.0.5 <3.1.7", - "elkjs": "^0.9.0", - "katex": "^0.16.9", - "khroma": "^2.0.0", - "lodash-es": "^4.17.21", - "mdast-util-from-markdown": "^1.3.0", - "non-layered-tidy-tree-layout": "^2.0.2", - "stylis": "^4.1.3", - "ts-dedent": "^2.2.0", - "uuid": "^9.0.0", - "web-worker": "^1.2.0" - } - }, - "node_modules/@excalidraw/mermaid-to-excalidraw/node_modules/nanoid": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", - "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/@excalidraw/mermaid-to-excalidraw/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@excalidraw/random-username": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@excalidraw/random-username/-/random-username-1.1.0.tgz", - "integrity": "sha512-nULYsQxkWHnbmHvcs+efMkJ4/9TtvNyFeLyHdeGxW0zHs6P+jYVqcRff9A6Vq9w9JXeDRnRh2VKvTtS19GW2qA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/@floating-ui/core": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", @@ -2002,31 +1081,6 @@ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, - "node_modules/@hocuspocus/common": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@hocuspocus/common/-/common-3.4.4.tgz", - "integrity": "sha512-RykIJ0tsHHMP4Xk+4UCbc7SO5LgGxGUSTdbh6anJEsaALAyqinf1Nn5HYuMjLPolAmsar1v++m9zufR09NLpXA==", - "license": "MIT", - "dependencies": { - "lib0": "^0.2.87" - } - }, - "node_modules/@hocuspocus/provider": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@hocuspocus/provider/-/provider-3.4.4.tgz", - "integrity": "sha512-KbsMAfdYcIJD8eMU/5QnpXcSOvIWAcCNI33FSRSaKCIpYBFtAwkYIwWnZJmPZ8a1BMAtqQc+uvy9+UQf7GHnGQ==", - "license": "MIT", - "dependencies": { - "@hocuspocus/common": "^3.4.4", - "@lifeomic/attempt": "^3.0.2", - "lib0": "^0.2.87", - "ws": "^8.17.1" - }, - "peerDependencies": { - "y-protocols": "^1.0.6", - "yjs": "^13.6.8" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2079,23 +1133,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "license": "MIT" - }, - "node_modules/@iconify/utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", - "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@iconify/types": "^2.0.0", - "mlly": "^1.8.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2160,21 +1197,6 @@ } } }, - "node_modules/@lifeomic/attempt": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@lifeomic/attempt/-/attempt-3.1.0.tgz", - "integrity": "sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw==", - "license": "MIT" - }, - "node_modules/@mermaid-js/parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.1.tgz", - "integrity": "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ==", - "license": "MIT", - "dependencies": { - "langium": "^4.0.0" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2294,36 +1316,6 @@ } } }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", - "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", @@ -2563,15 +1555,6 @@ } } }, - "node_modules/@radix-ui/react-icons": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", - "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", - "license": "MIT", - "peerDependencies": { - "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" - } - }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -2590,52 +1573,6 @@ } } }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", - "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-menu": { "version": "2.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", @@ -2985,52 +1922,6 @@ } } }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", - "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-slot": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", @@ -3108,88 +1999,6 @@ } } }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", - "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", - "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", - "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", @@ -3873,15 +2682,6 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, - "node_modules/@swc/helpers": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", - "integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/@tanstack/query-core": { "version": "5.90.20", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", @@ -4141,37 +2941,6 @@ "@tiptap/pm": "^3.20.4" } }, - "node_modules/@tiptap/extension-collaboration": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-3.20.4.tgz", - "integrity": "sha512-6KSZ5hCpscT4losDeRbZ2G9xifgMvSGJZnw+tFPgmNrbZToKMSbPnKMUTx4DYUiB2yTDv/fJOrcCFaQ5EpbHBA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/pm": "^3.20.4", - "@tiptap/y-tiptap": "^3.0.2", - "yjs": "^13" - } - }, - "node_modules/@tiptap/extension-collaboration-caret": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-caret/-/extension-collaboration-caret-3.20.4.tgz", - "integrity": "sha512-JuuNOhwE6fbbJDNKqU5ul+KC5ddWdZNWlJdLgGA6K6Z8btyOMZKcWtT6rLjYYHmojP4qZ1K/niMBy2/n8B5ZcQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/pm": "^3.20.4", - "@tiptap/y-tiptap": "^3.0.2" - } - }, "node_modules/@tiptap/extension-color": { "version": "3.20.4", "resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-3.20.4.tgz", @@ -4198,43 +2967,6 @@ "@tiptap/core": "^3.20.4" } }, - "node_modules/@tiptap/extension-drag-handle": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-drag-handle/-/extension-drag-handle-3.20.4.tgz", - "integrity": "sha512-qVf+fuvT7Dh7P3hwhowrds6AX3l/BO/Uppf+q3OL49G9URkYZr77ZuP6TsZAwRxylyJ6RVLAFScODXnX0b/kfQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.6.13" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/extension-collaboration": "^3.20.4", - "@tiptap/extension-node-range": "^3.20.4", - "@tiptap/pm": "^3.20.4", - "@tiptap/y-tiptap": "^3.0.2" - } - }, - "node_modules/@tiptap/extension-drag-handle-react": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-drag-handle-react/-/extension-drag-handle-react-3.20.4.tgz", - "integrity": "sha512-GSVeAVku4VmDJesGseINqAdQLShiIgeJYa4xFGOj4fhBAwQG9P8aqOn9mIC8m/ys/Qt+mOu3Ry+zy1jtUnQoWA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extension-drag-handle": "^3.20.4", - "@tiptap/pm": "^3.20.4", - "@tiptap/react": "^3.20.4", - "react": "^16.8 || ^17 || ^18 || ^19", - "react-dom": "^16.8 || ^17 || ^18 || ^19" - } - }, "node_modules/@tiptap/extension-dropcursor": { "version": "3.20.4", "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.20.4.tgz", @@ -4248,26 +2980,6 @@ "@tiptap/extensions": "^3.20.4" } }, - "node_modules/@tiptap/extension-emoji": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-emoji/-/extension-emoji-3.20.4.tgz", - "integrity": "sha512-dNJfmAtwSp8TEclLyM/UV/46jsrgETmmlGDEU5x9ZzE95lzcLQElCuRb4wA5Yag4sOXMRX1K3bAOgak8S3mmnQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.4.0", - "emojibase-data": "^15", - "is-emoji-supported": "^0.0.5" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/pm": "^3.20.4", - "@tiptap/suggestion": "^3.20.4" - } - }, "node_modules/@tiptap/extension-floating-menu": { "version": "3.20.4", "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.20.4.tgz", @@ -4433,35 +3145,6 @@ "@tiptap/extension-list": "^3.20.4" } }, - "node_modules/@tiptap/extension-mention": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-3.20.4.tgz", - "integrity": "sha512-lrd4AwWJljjuob6SM7hRndVeikBJGmFCX8Ptm6qmfC4p7krT8OgSvMBXwrS2YpQQkGHAoT/dqbBbmVM6tt40sA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/pm": "^3.20.4", - "@tiptap/suggestion": "^3.20.4" - } - }, - "node_modules/@tiptap/extension-node-range": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-node-range/-/extension-node-range-3.20.4.tgz", - "integrity": "sha512-ymDNgsM+EVeoQP/bixMR4iiXK/Sp/osRSmOhcSNAXllzTV1QogEClessU/z7gl26H0XLhMkxzKIYCaUT0LXTbA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/pm": "^3.20.4" - } - }, "node_modules/@tiptap/extension-ordered-list": { "version": "3.20.4", "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.20.4.tgz", @@ -4514,74 +3197,6 @@ "@tiptap/core": "^3.20.4" } }, - "node_modules/@tiptap/extension-subscript": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-3.20.4.tgz", - "integrity": "sha512-Sx7YyqovrbMB7bFjWkAkMH1iV2XhlgVuJ5Mz1q5jbnPHb1kB2I1vIRWxlh22FtJhSYxHlkGH69OBn9In/zsycg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/pm": "^3.20.4" - } - }, - "node_modules/@tiptap/extension-superscript": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-3.20.4.tgz", - "integrity": "sha512-eJUAf8HJAaT/NeHPYeDogKd/3YWy5sMOGKktp+Gk/5t6XrLGy6KYs/avfcNuFg2QJwSCWpLaZBVfmbdLJI0rxg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/pm": "^3.20.4" - } - }, - "node_modules/@tiptap/extension-table": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.20.4.tgz", - "integrity": "sha512-vEHXRL9k9G02pp3P+DyUnN4YRaRAHGfTBC6gck0s9TpsCM9NIchL0qI1fb/u46Bu6UaoMMk58DGr7xaJ29g7KQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/pm": "^3.20.4" - } - }, - "node_modules/@tiptap/extension-task-item": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-3.20.4.tgz", - "integrity": "sha512-mEWyAtZ61USZnKyLDxi2DtnSREfW0yUFXDOFWstNg1i6hva197BuAy6VRQMQxTOq+cFAgAt1MEZKanW0Obsa+g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extension-list": "^3.20.4" - } - }, - "node_modules/@tiptap/extension-task-list": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-3.20.4.tgz", - "integrity": "sha512-QvLrpffkxkr7TTgMmk6fnPAE34HYrUosHiuZJpRK008MuJDOoANblS221M4lLuRE73w3KI7hd/fi2CliBcCC4A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extension-list": "^3.20.4" - } - }, "node_modules/@tiptap/extension-text": { "version": "3.20.4", "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.20.4.tgz", @@ -4741,40 +3356,6 @@ "url": "https://github.com/sponsors/ueberdosis" } }, - "node_modules/@tiptap/suggestion": { - "version": "3.20.4", - "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.20.4.tgz", - "integrity": "sha512-7uqgLwjEAvsosrfoVrYVBQtAKI2pJFHOM8wgsQMLv1pQL8mfRz5ByD2xet4DOKb23q4mDvrvzp6z0kvCp0hMDw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.4", - "@tiptap/pm": "^3.20.4" - } - }, - "node_modules/@tiptap/y-tiptap": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@tiptap/y-tiptap/-/y-tiptap-3.0.2.tgz", - "integrity": "sha512-flMn/YW6zTbc6cvDaUPh/NfLRTXDIqgpBUkYzM74KA1snqQwhOMjnRcnpu4hDFrTnPO6QGzr99vRyXEA7M44WA==", - "license": "MIT", - "dependencies": { - "lib0": "^0.2.100" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1.7.1", - "prosemirror-state": "^1.2.3", - "prosemirror-view": "^1.9.10", - "y-protocols": "^1.0.1", - "yjs": "^13.5.38" - } - }, "node_modules/@twemoji/api": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/@twemoji/api/-/api-17.0.2.tgz", @@ -4838,159 +3419,24 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" - } - }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", "license": "MIT" }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "license": "MIT" - }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "license": "MIT" }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "license": "MIT" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", - "license": "MIT" - }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "license": "MIT" }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", - "license": "MIT", - "dependencies": { - "@types/d3-dsv": "*" - } - }, - "node_modules/@types/d3-force": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", - "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", - "license": "MIT" - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", - "license": "MIT" - }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", - "license": "MIT" - }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", @@ -5006,24 +3452,6 @@ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "license": "MIT" }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", - "license": "MIT" - }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", - "license": "MIT" - }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", - "license": "MIT" - }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", @@ -5033,18 +3461,6 @@ "@types/d3-time": "*" } }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", - "license": "MIT" - }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, "node_modules/@types/d3-shape": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", @@ -5060,46 +3476,12 @@ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", "license": "MIT" }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", - "license": "MIT" - }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -5117,12 +3499,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -5146,31 +3522,17 @@ "@types/mdurl": "^2" } }, - "node_modules/@types/mdast": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", - "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2" - } - }, "node_modules/@types/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "license": "MIT" }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, "node_modules/@types/node": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.18.0" @@ -5211,12 +3573,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -5492,16 +3848,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@upsetjs/venn.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", - "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", - "license": "MIT", - "optionalDependencies": { - "d3-selection": "^3.0.0", - "d3-transition": "^3.0.1" - } - }, "node_modules/@vitejs/plugin-react": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.3.tgz", @@ -5527,6 +3873,7 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5562,12 +3909,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/alien-signals": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", - "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", - "license": "MIT" - }, "node_modules/ansi-escapes": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", @@ -5624,6 +3965,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -5658,22 +4000,6 @@ "node": ">=10" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5716,21 +4042,6 @@ "postcss": "^8.1.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/axios": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", @@ -5767,20 +4078,11 @@ "error-kid": "^0.0.7" } }, - "node_modules/bezier-js": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", - "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", - "license": "MIT", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5804,6 +4106,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -5812,12 +4115,6 @@ "node": ">=8" } }, - "node_modules/browser-fs-access": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.29.1.tgz", - "integrity": "sha512-LSvVX5e21LRrXqVMhqtAwj5xPgDb+fXAIH80NsnCQ9xuZPs2xWsOREi24RKgZa1XOiQRbcmVrv87+ulOKsgjxw==", - "license": "Apache-2.0" - }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -5851,36 +4148,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer-image-size": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", - "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -5894,22 +4161,6 @@ "node": ">= 0.4" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5950,12 +4201,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvas-roundrect-polyfill": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/canvas-roundrect-polyfill/-/canvas-roundrect-polyfill-0.0.1.tgz", - "integrity": "sha512-yWq+R3U3jE+coOeEb3a3GgE2j/0MMiDKM/QpLb6h9ihf5fGY9UXtvK9o4vNqjWXoZz7/3EaSVU3IX53TvFFUOw==", - "license": "MIT" - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5973,46 +4218,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chevrotain": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", - "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "11.1.2", - "@chevrotain/gast": "11.1.2", - "@chevrotain/regexp-to-ast": "11.1.2", - "@chevrotain/types": "11.1.2", - "@chevrotain/utils": "11.1.2", - "lodash-es": "4.17.23" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.17.21" - }, - "peerDependencies": { - "chevrotain": "^11.0.0" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -6037,6 +4247,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -6164,12 +4375,6 @@ "node": ">=20" } }, - "node_modules/compute-scroll-into-view": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", - "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6177,12 +4382,6 @@ "dev": true, "license": "MIT" }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -6203,58 +4402,17 @@ "url": "https://opencollective.com/express" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cose-base": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", - "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", - "license": "MIT", - "dependencies": { - "layout-base": "^1.0.0" - } - }, - "node_modules/crc-32": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-0.3.0.tgz", - "integrity": "sha512-kucVIjOmMc1f0tv53BJ/5WIX+MGLcKuoBhnGqQrgKJNqLByb/sVMWfW/Aw6hw0jgcqjJ2pi9E5y32zOIpaUlsA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "license": "MIT" }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6284,95 +4442,6 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, - "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/cytoscape-cose-bilkent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", - "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^1.0.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^2.2.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "license": "MIT", - "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "license": "MIT" - }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -6385,43 +4454,6 @@ "node": ">=12" } }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -6431,86 +4463,6 @@ "node": ">=12" } }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -6520,32 +4472,6 @@ "node": ">=12" } }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-format": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", @@ -6555,27 +4481,6 @@ "node": ">=12" } }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/d3-interpolate": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", @@ -6597,73 +4502,6 @@ "node": ">=12" } }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "license": "BSD-3-Clause" - }, - "node_modules/d3-sankey/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" - } - }, - "node_modules/d3-sankey/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -6680,28 +4518,6 @@ "node": ">=12" } }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/d3-shape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", @@ -6747,61 +4563,11 @@ "node": ">=12" } }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dagre-d3-es": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", - "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "lodash-es": "^4.17.21" - } - }, - "node_modules/dayjs": { - "version": "1.11.20", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", - "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -6821,51 +4587,6 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6873,49 +4594,6 @@ "dev": true, "license": "MIT" }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6947,15 +4625,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/diff": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", - "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -6963,41 +4632,6 @@ "dev": true, "license": "MIT" }, - "node_modules/docx": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/docx/-/docx-9.6.1.tgz", - "integrity": "sha512-ZJja9/KBUuFC109sCMzovoq2GR2wCG/AuxivjA+OHj/q0TEgJIm3S7yrlUxIy3B+bV8YDj/BiHfWyrRFmyWpDQ==", - "license": "MIT", - "dependencies": { - "@types/node": "^25.2.3", - "hash.js": "^1.1.7", - "jszip": "^3.10.1", - "nanoid": "^5.1.3", - "xml": "^1.0.1", - "xml-js": "^1.6.8" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/docx/node_modules/nanoid": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.7.tgz", - "integrity": "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, "node_modules/dompurify": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", @@ -7021,60 +4655,19 @@ "node": ">= 0.4" } }, - "node_modules/easydrawer": { - "version": "0.0.15", - "resolved": "https://registry.npmjs.org/easydrawer/-/easydrawer-0.0.15.tgz", - "integrity": "sha512-dRA7I4kXI7wPBQCxzpcDj4JlNAogCISaGiAO1YmcPB3W8sGB2LusULr8S2M/jeIJdfbKkPAY9dpevlr37m8NUw==", - "license": "MIT", - "dependencies": { - "bezier-js": "^6.1.4" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.286", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "license": "ISC" }, - "node_modules/elkjs": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", - "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", - "license": "EPL-2.0" - }, "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/emojibase": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/emojibase/-/emojibase-17.0.0.tgz", - "integrity": "sha512-bXdpf4HPY3p41zK5swVKZdC/VynsMZ4LoLxdYDE+GucqkFwzcM1GVc4ODfYAlwoKaf2U2oNNUoOO78N96ovpBA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "type": "ko-fi", - "url": "https://ko-fi.com/milesjohnson" - } - }, - "node_modules/emojibase-data": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/emojibase-data/-/emojibase-data-15.3.2.tgz", - "integrity": "sha512-TpDyTDDTdqWIJixV5sTA6OQ0P0JfIIeK2tFRR3q56G9LK65ylAZ7z3KyBXokpvTTJ+mLUXQXbLNyVkjvnTLE+A==", - "license": "MIT", - "funding": { - "type": "ko-fi", - "url": "https://ko-fi.com/milesjohnson" - }, - "peerDependencies": { - "emojibase": "*" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -7124,26 +4717,6 @@ "node": ">= 0.4" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -7181,15 +4754,6 @@ "benchmarks" ] }, - "node_modules/es6-promise-pool": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/es6-promise-pool/-/es6-promise-pool-2.5.0.tgz", - "integrity": "sha512-VHErXfzR/6r/+yyzPKeBvO0lgjfC5cbDCQWjWwMZWSb6YU39TGIl51OUmCfWCq4ylMdJSB8zkz2vIuIeIxXApA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/esbuild": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", @@ -7555,6 +5119,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -7621,21 +5186,6 @@ } } }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -7665,15 +5215,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fractional-indexing": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fractional-indexing/-/fractional-indexing-3.2.0.tgz", - "integrity": "sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==", - "license": "CC0-1.0", - "engines": { - "node": "^14.13.1 || >=16.0.0" - } - }, "node_modules/framer-motion": { "version": "12.33.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.33.0.tgz", @@ -7701,25 +5242,6 @@ } } }, - "node_modules/frimousse": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/frimousse/-/frimousse-0.3.0.tgz", - "integrity": "sha512-kO6LMoKY/cLAYEhXXtqLRaLIE6L/DagpFPrUZaLv3LsUa1/8Iza3HhwZcgN8eZ+weXnhv69eoclNUPohcCa/IQ==", - "license": "MIT", - "workspaces": [ - ".", - "site" - ], - "peerDependencies": { - "react": "^18 || ^19", - "typescript": ">=5.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -7747,6 +5269,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7766,23 +5289,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fuzzy": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz", - "integrity": "sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w==", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -7878,12 +5384,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glur": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz", - "integrity": "sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==", - "license": "MIT" - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -7941,24 +5441,6 @@ "graphology-types": ">=0.23.0" } }, - "node_modules/hachure-fill": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", - "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", - "license": "MIT" - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7969,18 +5451,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -8008,16 +5478,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -8112,18 +5572,6 @@ "@babel/runtime": "^7.23.2" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -8134,21 +5582,6 @@ "node": ">= 4" } }, - "node_modules/image-blob-reduce": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/image-blob-reduce/-/image-blob-reduce-3.0.1.tgz", - "integrity": "sha512-/VmmWgIryG/wcn4TVrV7cC4mlfUC/oyiKIfSg5eVM3Ten/c1c34RJhMYKCWTnoSMHSqXLt3tsrBR4Q2HInvN+Q==", - "license": "MIT", - "dependencies": { - "pica": "^7.1.0" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, "node_modules/immer": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", @@ -8186,26 +5619,6 @@ "node": ">=0.8.19" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -8215,58 +5628,11 @@ "node": ">=12" } }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -8275,34 +5641,6 @@ "node": ">=8" } }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -8319,32 +5657,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-emoji-supported": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/is-emoji-supported/-/is-emoji-supported-0.0.5.tgz", - "integrity": "sha512-WOlXUhDDHxYqcSmFZis+xWhhqXiK2SU0iYiqmth5Ip0FHLZQAt9rKL5ahnilE8/86WH8tZ3bmNNNC+bTzamqlw==", - "license": "MIT" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8370,6 +5687,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -8378,171 +5696,23 @@ "node": ">=0.10.0" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, - "node_modules/isomorphic.js": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", - "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", - "license": "MIT", - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -8553,37 +5723,6 @@ "jiti": "bin/jiti.js" } }, - "node_modules/jotai": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.11.0.tgz", - "integrity": "sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=17.0.0", - "react": ">=17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/jotai-scope": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/jotai-scope/-/jotai-scope-0.7.2.tgz", - "integrity": "sha512-Gwed97f3dDObrO43++2lRcgOqw4O2sdr4JCjP/7eHK1oPACDJ7xKHGScpJX9XaflU+KBHXF+VhwECnzcaQiShg==", - "license": "MIT", - "peerDependencies": { - "jotai": ">=2.9.2", - "react": ">=17.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8669,49 +5808,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/jszip/node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, - "node_modules/katex": { - "version": "0.16.40", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.40.tgz", - "integrity": "sha512-1DJcK/L05k1Y9Gf7wMcyuqFOL6BiY3vY0CFcAM/LPRN04NALxcl6u7lOWNsp3f/bCHWxigzQl6FbR95XJ4R84Q==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -8722,43 +5818,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/khroma": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", - "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/langium": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", - "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", - "license": "MIT", - "dependencies": { - "chevrotain": "~11.1.1", - "chevrotain-allstar": "~0.3.1", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.1.0" - }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" - } - }, - "node_modules/layout-base": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", - "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", - "license": "MIT" - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8773,36 +5832,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lib0": { - "version": "0.2.117", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", - "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", - "license": "MIT", - "dependencies": { - "isomorphic.js": "^0.2.4" - }, - "bin": { - "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", - "0gentesthtml": "bin/gentesthtml.js", - "0serve": "bin/0serve.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -8897,18 +5926,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -8916,12 +5933,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT" - }, "node_modules/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", @@ -8952,15 +5963,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lucide-react": { - "version": "0.427.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.427.0.tgz", - "integrity": "sha512-lv9s6c5BDF/ccuA0EgTdskTxIe11qpwBDmzRZHJAKtp8LTewAvDvOM+pTES9IpbBuTqkjiMhOmGpJ/CB+mKjFw==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" - } - }, "node_modules/markdown-it": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", @@ -8978,18 +5980,6 @@ "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -8999,43 +5989,6 @@ "node": ">= 0.4" } }, - "node_modules/mdast-util-from-markdown": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", - "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "mdast-util-to-string": "^3.1.0", - "micromark": "^3.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-decode-string": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -9052,513 +6005,6 @@ "node": ">= 8" } }, - "node_modules/mermaid": { - "version": "11.13.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.13.0.tgz", - "integrity": "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.2", - "@mermaid-js/parser": "^1.0.1", - "@types/d3": "^7.4.3", - "@upsetjs/venn.js": "^2.0.0", - "cytoscape": "^3.33.1", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.2.0", - "d3": "^7.9.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.14", - "dayjs": "^1.11.19", - "dompurify": "^3.3.1", - "katex": "^0.16.25", - "khroma": "^2.1.0", - "lodash-es": "^4.17.23", - "marked": "^16.3.0", - "roughjs": "^4.6.6", - "stylis": "^4.3.6", - "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" - } - }, - "node_modules/mermaid/node_modules/@braintree/sanitize-url": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", - "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", - "license": "MIT" - }, - "node_modules/mermaid/node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/mermaid/node_modules/points-on-curve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", - "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", - "license": "MIT" - }, - "node_modules/mermaid/node_modules/roughjs": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", - "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", - "license": "MIT", - "dependencies": { - "hachure-fill": "^0.5.2", - "path-data-parser": "^0.1.0", - "points-on-curve": "^0.2.0", - "points-on-path": "^0.2.1" - } - }, - "node_modules/micromark": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", - "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "micromark-core-commonmark": "^1.0.1", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", - "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-factory-destination": "^1.0.0", - "micromark-factory-label": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-factory-title": "^1.0.0", - "micromark-factory-whitespace": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-html-tag-name": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-factory-destination": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", - "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", - "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", - "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", - "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", - "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", - "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", - "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", - "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", - "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", - "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", - "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", - "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", - "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", - "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", - "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -9607,12 +6053,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -9632,18 +6072,6 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "license": "MIT" }, - "node_modules/mlly": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", - "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", - "license": "MIT", - "dependencies": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" - } - }, "node_modules/motion-dom": { "version": "12.33.0", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.33.0.tgz", @@ -9659,31 +6087,13 @@ "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", "license": "MIT" }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, - "node_modules/multimath": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/multimath/-/multimath-2.0.0.tgz", - "integrity": "sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g==", - "license": "MIT", - "dependencies": { - "glur": "^1.1.2", - "object-assign": "^4.1.1" - } - }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -9740,16 +6150,11 @@ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT" }, - "node_modules/non-layered-tidy-tree-layout": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", - "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", - "license": "MIT" - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9759,6 +6164,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9774,63 +6180,6 @@ "node": ">= 6" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -9847,12 +6196,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open-color": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/open-color/-/open-color-1.9.1.tgz", - "integrity": "sha512-vCseG/EQ6/RcvxhUcGJiHViOgrtz4x0XbZepXvKik66TMGkvbmjeJrKFyBEx6daG5rNyyd14zYXhz0hZVwQFOw==", - "license": "MIT" - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -9909,18 +6252,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-manager-detector": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", - "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", - "license": "MIT" - }, - "node_modules/pako": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.3.tgz", - "integrity": "sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw==", - "license": "(MIT AND Zlib)" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9934,12 +6265,6 @@ "node": ">=6" } }, - "node_modules/path-data-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", - "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", - "license": "MIT" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9954,6 +6279,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9966,31 +6292,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/perfect-freehand": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.2.0.tgz", - "integrity": "sha512-h/0ikF1M3phW7CwpZ5MMvKnfpHficWoOEyr//KVNTxV4F6deRK1eYMtHyBKEAKFK0aXIEUK9oBvlF6PNXMDsAw==", - "license": "MIT" - }, - "node_modules/pica": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/pica/-/pica-7.1.1.tgz", - "integrity": "sha512-WY73tMvNzXWEld2LicT9Y260L43isrZ85tPuqRyvtkljSDLmnNFQmZICt4xUJMVulmcc6L9O7jbBrtx3DOz/YQ==", - "license": "MIT", - "dependencies": { - "glur": "^1.1.2", - "inherits": "^2.0.3", - "multimath": "^2.0.0", - "object-assign": "^4.1.1", - "webworkify": "^1.5.0" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10001,6 +6302,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -10042,73 +6344,6 @@ "node": ">= 6" } }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/png-chunk-text": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/png-chunk-text/-/png-chunk-text-1.0.0.tgz", - "integrity": "sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw==", - "license": "MIT" - }, - "node_modules/png-chunks-encode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/png-chunks-encode/-/png-chunks-encode-1.0.0.tgz", - "integrity": "sha512-J1jcHgbQRsIIgx5wxW9UmCymV3wwn4qCCJl6KYgEU/yHCh/L2Mwq/nMOkRPtmV79TLxRZj5w3tH69pvygFkDqA==", - "license": "MIT", - "dependencies": { - "crc-32": "^0.3.0", - "sliced": "^1.0.1" - } - }, - "node_modules/png-chunks-extract": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/png-chunks-extract/-/png-chunks-extract-1.0.0.tgz", - "integrity": "sha512-ZiVwF5EJ0DNZyzAqld8BP1qyJBaGOFaq9zl579qfbkcmOwWLLO4I9L8i2O4j3HkI6/35i0nKG2n+dZplxiT89Q==", - "license": "MIT", - "dependencies": { - "crc-32": "^0.3.0" - } - }, - "node_modules/points-on-curve": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-1.0.1.tgz", - "integrity": "sha512-3nmX4/LIiyuwGLwuUrfhTlDeQFlAhi7lyK/zcRNGhalwapDWgAGR82bUpmn2mA03vII3fvNCG8jAONzKXwpxAg==", - "license": "MIT" - }, - "node_modules/points-on-path": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", - "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", - "license": "MIT", - "dependencies": { - "path-data-parser": "0.1.0", - "points-on-curve": "0.2.0" - } - }, - "node_modules/points-on-path/node_modules/points-on-curve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", - "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", - "license": "MIT" - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -10375,18 +6610,6 @@ } } }, - "node_modules/prism-code-editor-lightweight": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/prism-code-editor-lightweight/-/prism-code-editor-lightweight-0.0.3.tgz", - "integrity": "sha512-J1DO4ru/hAmHh5FUPMvh2GWcEKjb9XsvvKA59YtrVeB4fccDXXXZAHNMlJKg2SevgyGDSB/pCJ3H7t09aLNbvw==", - "license": "MIT" - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, "node_modules/prosemirror-changeset": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", @@ -10416,19 +6639,6 @@ "prosemirror-transform": "^1.10.2" } }, - "node_modules/prosemirror-docx": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/prosemirror-docx/-/prosemirror-docx-0.2.1.tgz", - "integrity": "sha512-W0FFEzsZ9/qcoS4a2+6t8HUXckjiilFged1B4X5FMOqQI1w2+iV8EWkcZoKcePcrLndXMxdQDMR4BJBrpmihCA==", - "license": "MIT", - "dependencies": { - "buffer-image-size": "^0.6.4", - "prosemirror-model": "^1.18.1" - }, - "peerDependencies": { - "docx": "^8.5.0 || ^9.0.0" - } - }, "node_modules/prosemirror-dropcursor": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", @@ -10620,12 +6830,6 @@ "node": ">=6" } }, - "node_modules/pwacompat": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/pwacompat/-/pwacompat-2.0.17.tgz", - "integrity": "sha512-6Du7IZdIy7cHiv7AhtDy4X2QRM8IAD5DII69mt5qWibC2d15ZU8DmBG1WdZKekG11cChSu4zkSUGPF9sweOl6w==", - "license": "Apache-2.0" - }, "node_modules/qrcode.react": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", @@ -10656,16 +6860,6 @@ ], "license": "MIT" }, - "node_modules/re-resizable": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", - "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", - "license": "MIT", - "peerDependencies": { - "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -10675,16 +6869,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-colorful": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", - "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, "node_modules/react-dom": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", @@ -10724,15 +6908,6 @@ } } }, - "node_modules/react-image-crop": { - "version": "11.0.10", - "resolved": "https://registry.npmjs.org/react-image-crop/-/react-image-crop-11.0.10.tgz", - "integrity": "sha512-+5FfDXUgYLLqBh1Y/uQhIycpHCbXkI50a+nbfkB1C0xXXUTwkisHDo2QCB1SQJyHCqIuia4FeyReqXuMDKWQTQ==", - "license": "ISC", - "peerDependencies": { - "react": ">=16.13.1" - } - }, "node_modules/react-is": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", @@ -10864,21 +7039,6 @@ } } }, - "node_modules/react-tweet": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/react-tweet/-/react-tweet-3.3.0.tgz", - "integrity": "sha512-gSIG2169ZK7UH6rBzuU+j1xnQbH3IlOTLEkuGrRiJJTMgETik+h+26yHyyVKrLkzwrOaYPk4K3OtEKycqKgNLw==", - "license": "MIT", - "dependencies": { - "@swc/helpers": "^0.5.3", - "clsx": "^2.0.0", - "swr": "^2.2.4" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, "node_modules/react-twemoji": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/react-twemoji/-/react-twemoji-0.7.2.tgz", @@ -10896,121 +7056,6 @@ "react-dom": ">=19.0.0" } }, - "node_modules/reactjs-signal": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/reactjs-signal/-/reactjs-signal-2.0.4.tgz", - "integrity": "sha512-0cCCTZoOqQBwoQGFHVtvERk2wo2VLwazMszFn3icMV56q+vG1JRi+SvJ9kANHUWrJifsUcCh3nIXHhXo6LvXcA==", - "license": "ISC", - "dependencies": { - "alien-signals": "^3.1.2" - }, - "peerDependencies": { - "alien-signals": ">=3.1.2", - "react": ">=18" - } - }, - "node_modules/reactjs-tiptap-editor": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/reactjs-tiptap-editor/-/reactjs-tiptap-editor-1.0.19.tgz", - "integrity": "sha512-EelCkKzWE7RFMQxsq2sqPopwhQt6fWbiXQxexZ4Reuk4QoHysHDH4lnysyWOuf+kJA61qpVvabPtPdocCrmICA==", - "license": "MIT", - "dependencies": { - "@excalidraw/excalidraw": "^0.18.0", - "@floating-ui/dom": "^1.7.6", - "@hocuspocus/provider": "^3.4.4", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-toast": "^1.2.15", - "@radix-ui/react-toggle": "^1.1.10", - "@radix-ui/react-tooltip": "^1.2.8", - "@tiptap/core": "^3.20.0", - "@tiptap/extension-blockquote": "^3.20.0", - "@tiptap/extension-bold": "^3.20.0", - "@tiptap/extension-bullet-list": "^3.20.0", - "@tiptap/extension-code": "^3.20.0", - "@tiptap/extension-collaboration": "^3.20.0", - "@tiptap/extension-collaboration-caret": "^3.20.0", - "@tiptap/extension-document": "^3.20.0", - "@tiptap/extension-drag-handle": "^3.20.0", - "@tiptap/extension-drag-handle-react": "^3.20.0", - "@tiptap/extension-emoji": "^3.20.0", - "@tiptap/extension-hard-break": "^3.20.0", - "@tiptap/extension-heading": "^3.20.0", - "@tiptap/extension-highlight": "^2.27.2", - "@tiptap/extension-horizontal-rule": "^3.20.0", - "@tiptap/extension-image": "^3.20.0", - "@tiptap/extension-italic": "^3.20.0", - "@tiptap/extension-link": "^3.20.0", - "@tiptap/extension-list": "^3.20.0", - "@tiptap/extension-list-item": "^3.20.0", - "@tiptap/extension-mention": "^3.20.0", - "@tiptap/extension-node-range": "^3.20.0", - "@tiptap/extension-ordered-list": "^3.20.0", - "@tiptap/extension-paragraph": "^3.20.0", - "@tiptap/extension-strike": "^3.20.0", - "@tiptap/extension-subscript": "^3.20.0", - "@tiptap/extension-superscript": "^3.20.0", - "@tiptap/extension-table": "^3.20.0", - "@tiptap/extension-task-item": "^3.20.0", - "@tiptap/extension-task-list": "^3.20.0", - "@tiptap/extension-text": "^3.20.0", - "@tiptap/extension-text-align": "^3.20.0", - "@tiptap/extension-text-style": "^3.20.0", - "@tiptap/extension-underline": "^3.20.0", - "@tiptap/extensions": "^3.20.0", - "@tiptap/pm": "^3.20.0", - "@tiptap/react": "^3.20.0", - "@tiptap/suggestion": "^3.20.0", - "@tiptap/y-tiptap": "^3.0.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "deep-equal": "^2.2.3", - "docx": "^9.6.0", - "easydrawer": "^0.0.15", - "frimousse": "^0.3.0", - "katex": "^0.16.33", - "lodash-es": "^4.17.23", - "lucide-react": "^0.427.0", - "marked": "^15.0.0", - "mermaid": "^11.12.3", - "prism-code-editor-lightweight": "^0.0.3", - "prosemirror-docx": "^0.2.1", - "re-resizable": "^6.11.2", - "react-colorful": "^5.6.1", - "react-image-crop": "^11.0.10", - "react-tweet": "^3.3.0", - "reactjs-signal": "^2.0.4", - "scroll-into-view-if-needed": "^3.1.0", - "svg64": "^2.0.0", - "y-protocols": "^1.0.7", - "yjs": "^13.6.29" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/reactjs-tiptap-editor/node_modules/@tiptap/extension-highlight": { - "version": "2.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.27.2.tgz", - "integrity": "sha512-ZjlktDdMjruMJFAVz0TbQf0v92Jqkc7Ri1iZJqBXuLid+r+GxUzl2CVAV7qq5yagkGQgvAG+WGsMk880HgR3MA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.7.0" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -11021,31 +7066,11 @@ "pify": "^2.3.0" } }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -11099,26 +7124,6 @@ "redux": "^5.0.0" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", @@ -11191,12 +7196,6 @@ "dev": true, "license": "MIT" }, - "node_modules/robust-predicates": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", - "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", - "license": "Unlicense" - }, "node_modules/rollup": { "version": "4.57.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", @@ -11248,24 +7247,6 @@ "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", "license": "MIT" }, - "node_modules/roughjs": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.4.tgz", - "integrity": "sha512-s6EZ0BntezkFYMf/9mGn7M8XGIoaav9QQBCnJROWB3brUWQ683Q2LbRD/hq0Z3bAJ/9NVpU/5LpiTWvQMyLDhw==", - "license": "MIT", - "dependencies": { - "hachure-fill": "^0.5.2", - "path-data-parser": "^0.1.0", - "points-on-curve": "^0.2.0", - "points-on-path": "^0.2.1" - } - }, - "node_modules/roughjs/node_modules/points-on-curve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", - "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", - "license": "MIT" - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -11290,77 +7271,12 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/sax": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", - "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, - "node_modules/scroll-into-view-if-needed": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", - "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", - "license": "MIT", - "dependencies": { - "compute-scroll-into-view": "^3.0.2" - } - }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -11377,48 +7293,11 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11431,83 +7310,12 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/sigma": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sigma/-/sigma-3.0.2.tgz", @@ -11567,12 +7375,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==", - "license": "MIT" - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -11582,28 +7384,6 @@ "node": ">=0.10.0" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -11660,12 +7440,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stylis": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" - }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -11725,28 +7499,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svg64": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/svg64/-/svg64-2.0.0.tgz", - "integrity": "sha512-EVgAisxMctUNDSjKGFcx4tkcFrvdqtLIy/MdbBdqcwfpPwsBcwoSKQi+WYoc82c4XWFNVVIwpCup3rpY+M9KJw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/scriptex" - } - }, - "node_modules/swr": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.4.1.tgz", - "integrity": "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3", - "use-sync-external-store": "^1.6.0" - }, - "peerDependencies": { - "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/tailwind-merge": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", @@ -11824,15 +7576,6 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, - "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -11885,6 +7628,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -11906,15 +7650,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "license": "MIT", - "engines": { - "node": ">=6.10" - } - }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -11928,43 +7663,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/tunnel-rat": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz", - "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==", - "license": "MIT", - "dependencies": { - "zustand": "^4.3.2" - } - }, - "node_modules/tunnel-rat/node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -12022,31 +7720,13 @@ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "license": "MIT" }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "license": "MIT" - }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, "license": "MIT" }, - "node_modules/unist-util-stringify-position": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", - "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -12152,39 +7832,9 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/uvu": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", - "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0", - "diff": "^5.0.0", - "kleur": "^4.0.3", - "sade": "^1.7.3" - }, - "bin": { - "uvu": "bin.js" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/valibot": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0.tgz", @@ -12336,77 +7986,17 @@ "node": ">=0.10.0" } }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "license": "MIT" - }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "license": "MIT" }, - "node_modules/web-worker": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", - "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", - "license": "Apache-2.0" - }, - "node_modules/webworkify": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/webworkify/-/webworkify-1.5.0.tgz", - "integrity": "sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g==", - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -12418,64 +8008,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -12535,65 +8067,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", - "license": "MIT" - }, - "node_modules/xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "license": "MIT", - "dependencies": { - "sax": "^1.2.4" - }, - "bin": { - "xml-js": "bin/cli.js" - } - }, - "node_modules/y-protocols": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz", - "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==", - "license": "MIT", - "dependencies": { - "lib0": "^0.2.85" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - }, - "peerDependencies": { - "yjs": "^13.0.0" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -12617,23 +8090,6 @@ "url": "https://github.com/sponsors/eemeli" } }, - "node_modules/yjs": { - "version": "13.6.30", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.30.tgz", - "integrity": "sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==", - "license": "MIT", - "dependencies": { - "lib0": "^0.2.99" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 096c0f2..2531271 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "react-i18next": "^16.5.4", "react-router": "^7.13.0", "react-twemoji": "^0.7.2", - "reactjs-tiptap-editor": "^1.0.19", "recharts": "^3.7.0", "sigma": "^3.0.2", "simplex-noise": "^4.0.3", diff --git a/src/App.tsx b/src/App.tsx index 4d2ab16..b030a73 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,25 @@ -import { lazy, Suspense } from 'react'; -import { Routes, Route, Navigate, useLocation } from 'react-router'; +import { lazy, Suspense, type ComponentType } from 'react'; +import { Routes, Route, Navigate, useLocation, useParams } from 'react-router'; import { useAuthStore } from './store/auth'; + +/** + * Wrapper around React.lazy that auto-reloads the page when a chunk fails to load + * (e.g. after a new deploy with different chunk hashes). + */ +function lazyWithRetry>(factory: () => Promise<{ default: T }>) { + return lazy(() => + factory().catch(() => { + const key = 'chunk_reload_ts'; + const last = Number(sessionStorage.getItem(key) || '0'); + if (Date.now() - last > 30_000) { + sessionStorage.setItem(key, String(Date.now())); + window.location.reload(); + } + // Re-throw so ErrorBoundary catches it if reload guard prevents loop + return factory(); + }), + ); +} import { useBlockingStore } from './store/blocking'; import Layout from './components/layout/Layout'; import PageLoader from './components/common/PageLoader'; @@ -26,101 +45,107 @@ import OAuthCallback from './pages/OAuthCallback'; import Dashboard from './pages/Dashboard'; // User pages - lazy load -const Subscription = lazy(() => import('./pages/Subscription')); -const SubscriptionPurchase = lazy(() => import('./pages/SubscriptionPurchase')); -const Balance = lazy(() => import('./pages/Balance')); -const SavedCards = lazy(() => import('./pages/SavedCards')); -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 GiftSubscription = lazy(() => import('./pages/GiftSubscription')); -const GiftResult = lazy(() => import('./pages/GiftResult')); -const Connection = lazy(() => import('./pages/Connection')); -const ConnectionQR = lazy(() => import('./pages/ConnectionQR')); -const QuickPurchase = lazy(() => import('./pages/QuickPurchase')); -const PurchaseSuccess = lazy(() => import('./pages/PurchaseSuccess')); -const AutoLogin = lazy(() => import('./pages/AutoLogin')); -const TopUpMethodSelect = lazy(() => import('./pages/TopUpMethodSelect')); -const TopUpAmount = lazy(() => import('./pages/TopUpAmount')); -const TopUpResult = lazy(() => import('./pages/TopUpResult')); -const ConnectedAccounts = lazy(() => import('./pages/ConnectedAccounts')); -const LinkTelegramCallback = lazy(() => import('./pages/LinkTelegramCallback')); -const MergeAccounts = lazy(() => import('./pages/MergeAccounts')); +const Subscriptions = lazyWithRetry(() => import('./pages/Subscriptions')); +const Subscription = lazyWithRetry(() => import('./pages/Subscription')); +const SubscriptionPurchase = lazyWithRetry(() => import('./pages/SubscriptionPurchase')); +const Balance = lazyWithRetry(() => import('./pages/Balance')); +const SavedCards = lazyWithRetry(() => import('./pages/SavedCards')); +const Referral = lazyWithRetry(() => import('./pages/Referral')); +const Support = lazyWithRetry(() => import('./pages/Support')); +const Profile = lazyWithRetry(() => import('./pages/Profile')); +const Contests = lazyWithRetry(() => import('./pages/Contests')); +const Polls = lazyWithRetry(() => import('./pages/Polls')); +const Info = lazyWithRetry(() => import('./pages/Info')); +const Wheel = lazyWithRetry(() => import('./pages/Wheel')); +const GiftSubscription = lazyWithRetry(() => import('./pages/GiftSubscription')); +const GiftResult = lazyWithRetry(() => import('./pages/GiftResult')); +const Connection = lazyWithRetry(() => import('./pages/Connection')); +const ConnectionQR = lazyWithRetry(() => import('./pages/ConnectionQR')); +const QuickPurchase = lazyWithRetry(() => import('./pages/QuickPurchase')); +const PurchaseSuccess = lazyWithRetry(() => import('./pages/PurchaseSuccess')); +const RenewSubscription = lazyWithRetry(() => import('./pages/RenewSubscription')); +const AutoLogin = lazyWithRetry(() => import('./pages/AutoLogin')); +const TopUpMethodSelect = lazyWithRetry(() => import('./pages/TopUpMethodSelect')); +const TopUpAmount = lazyWithRetry(() => import('./pages/TopUpAmount')); +const TopUpResult = lazyWithRetry(() => import('./pages/TopUpResult')); +const ConnectedAccounts = lazyWithRetry(() => import('./pages/ConnectedAccounts')); +const LinkTelegramCallback = lazyWithRetry(() => import('./pages/LinkTelegramCallback')); +const MergeAccounts = lazyWithRetry(() => import('./pages/MergeAccounts')); // Admin pages - lazy load (only for admins) -const AdminPanel = lazy(() => import('./pages/AdminPanel')); -const AdminTickets = lazy(() => import('./pages/AdminTickets')); -const AdminTicketSettings = lazy(() => import('./pages/AdminTicketSettings')); -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 AdminTariffCreate = lazy(() => import('./pages/AdminTariffCreate')); -const AdminServers = lazy(() => import('./pages/AdminServers')); -const AdminServerEdit = lazy(() => import('./pages/AdminServerEdit')); -const AdminDashboard = lazy(() => import('./pages/AdminDashboard')); -const AdminBanSystem = lazy(() => import('./pages/AdminBanSystem')); -const AdminBroadcasts = lazy(() => import('./pages/AdminBroadcasts')); -const AdminBroadcastCreate = lazy(() => import('./pages/AdminBroadcastCreate')); -const AdminPromocodes = lazy(() => import('./pages/AdminPromocodes')); -const AdminPromocodeCreate = lazy(() => import('./pages/AdminPromocodeCreate')); -const AdminPromocodeStats = lazy(() => import('./pages/AdminPromocodeStats')); -const AdminPromoGroups = lazy(() => import('./pages/AdminPromoGroups')); -const AdminPromoGroupCreate = lazy(() => import('./pages/AdminPromoGroupCreate')); -const AdminCampaigns = lazy(() => import('./pages/AdminCampaigns')); -const AdminCampaignCreate = lazy(() => import('./pages/AdminCampaignCreate')); -const AdminCampaignStats = lazy(() => import('./pages/AdminCampaignStats')); -const AdminCampaignEdit = lazy(() => import('./pages/AdminCampaignEdit')); -const AdminPartners = lazy(() => import('./pages/AdminPartners')); -const AdminPartnerSettings = lazy(() => import('./pages/AdminPartnerSettings')); -const AdminPartnerDetail = lazy(() => import('./pages/AdminPartnerDetail')); -const AdminApplicationReview = lazy(() => import('./pages/AdminApplicationReview')); -const AdminPartnerCommission = lazy(() => import('./pages/AdminPartnerCommission')); -const AdminPartnerRevoke = lazy(() => import('./pages/AdminPartnerRevoke')); -const AdminPartnerCampaignAssign = lazy(() => import('./pages/AdminPartnerCampaignAssign')); -const AdminWithdrawals = lazy(() => import('./pages/AdminWithdrawals')); -const AdminWithdrawalDetail = lazy(() => import('./pages/AdminWithdrawalDetail')); -const AdminWithdrawalReject = lazy(() => import('./pages/AdminWithdrawalReject')); -const ReferralPartnerApply = lazy(() => import('./pages/ReferralPartnerApply')); -const ReferralWithdrawalRequest = lazy(() => import('./pages/ReferralWithdrawalRequest')); -const AdminUsers = lazy(() => import('./pages/AdminUsers')); -const AdminPayments = lazy(() => import('./pages/AdminPayments')); -const AdminPaymentMethods = lazy(() => import('./pages/AdminPaymentMethods')); -const AdminPaymentMethodEdit = lazy(() => import('./pages/AdminPaymentMethodEdit')); -const AdminPromoOffers = lazy(() => import('./pages/AdminPromoOffers')); -const AdminPromoOfferTemplateEdit = lazy(() => import('./pages/AdminPromoOfferTemplateEdit')); -const AdminPromoOfferSend = lazy(() => import('./pages/AdminPromoOfferSend')); -const AdminRemnawave = lazy(() => import('./pages/AdminRemnawave')); -const AdminRemnawaveSquadDetail = lazy(() => import('./pages/AdminRemnawaveSquadDetail')); -const AdminEmailTemplates = lazy(() => import('./pages/AdminEmailTemplates')); -const AdminTrafficUsage = lazy(() => import('./pages/AdminTrafficUsage')); -const AdminSalesStats = lazy(() => import('./pages/AdminSalesStats')); -const AdminUpdates = lazy(() => import('./pages/AdminUpdates')); -const AdminUserDetail = lazy(() => import('./pages/AdminUserDetail')); -const AdminBroadcastDetail = lazy(() => import('./pages/AdminBroadcastDetail')); -const AdminPinnedMessages = lazy(() => import('./pages/AdminPinnedMessages')); -const AdminPinnedMessageCreate = lazy(() => import('./pages/AdminPinnedMessageCreate')); -const AdminChannelSubscriptions = lazy(() => import('./pages/AdminChannelSubscriptions')); -const AdminEmailTemplatePreview = lazy(() => import('./pages/AdminEmailTemplatePreview')); -const AdminRoles = lazy(() => import('./pages/AdminRoles')); -const AdminRoleEdit = lazy(() => import('./pages/AdminRoleEdit')); -const AdminRoleAssign = lazy(() => import('./pages/AdminRoleAssign')); -const AdminPolicies = lazy(() => import('./pages/AdminPolicies')); -const AdminPolicyEdit = lazy(() => import('./pages/AdminPolicyEdit')); -const AdminAuditLog = lazy(() => import('./pages/AdminAuditLog')); -const AdminLandings = lazy(() => import('./pages/AdminLandings')); -const AdminLandingEditor = lazy(() => import('./pages/AdminLandingEditor')); -const AdminLandingStats = lazy(() => import('./pages/AdminLandingStats')); -const AdminReferralNetwork = lazy(() => import('./pages/ReferralNetwork')); +const AdminPanel = lazyWithRetry(() => import('./pages/AdminPanel')); +const AdminTickets = lazyWithRetry(() => import('./pages/AdminTickets')); +const AdminTicketSettings = lazyWithRetry(() => import('./pages/AdminTicketSettings')); +const AdminSettings = lazyWithRetry(() => import('./pages/AdminSettings')); +const AdminApps = lazyWithRetry(() => import('./pages/AdminApps')); +const AdminWheel = lazyWithRetry(() => import('./pages/AdminWheel')); +const AdminTariffs = lazyWithRetry(() => import('./pages/AdminTariffs')); +const AdminTariffCreate = lazyWithRetry(() => import('./pages/AdminTariffCreate')); +const AdminServers = lazyWithRetry(() => import('./pages/AdminServers')); +const AdminServerEdit = lazyWithRetry(() => import('./pages/AdminServerEdit')); +const AdminDashboard = lazyWithRetry(() => import('./pages/AdminDashboard')); +const AdminBanSystem = lazyWithRetry(() => import('./pages/AdminBanSystem')); +const AdminBroadcasts = lazyWithRetry(() => import('./pages/AdminBroadcasts')); +const AdminBroadcastCreate = lazyWithRetry(() => import('./pages/AdminBroadcastCreate')); +const AdminPromocodes = lazyWithRetry(() => import('./pages/AdminPromocodes')); +const AdminPromocodeCreate = lazyWithRetry(() => import('./pages/AdminPromocodeCreate')); +const AdminPromocodeStats = lazyWithRetry(() => import('./pages/AdminPromocodeStats')); +const AdminPromoGroups = lazyWithRetry(() => import('./pages/AdminPromoGroups')); +const AdminPromoGroupCreate = lazyWithRetry(() => import('./pages/AdminPromoGroupCreate')); +const AdminCampaigns = lazyWithRetry(() => import('./pages/AdminCampaigns')); +const AdminCampaignCreate = lazyWithRetry(() => import('./pages/AdminCampaignCreate')); +const AdminCampaignStats = lazyWithRetry(() => import('./pages/AdminCampaignStats')); +const AdminCampaignEdit = lazyWithRetry(() => import('./pages/AdminCampaignEdit')); +const AdminPartners = lazyWithRetry(() => import('./pages/AdminPartners')); +const AdminPartnerSettings = lazyWithRetry(() => import('./pages/AdminPartnerSettings')); +const AdminPartnerDetail = lazyWithRetry(() => import('./pages/AdminPartnerDetail')); +const AdminApplicationReview = lazyWithRetry(() => import('./pages/AdminApplicationReview')); +const AdminPartnerCommission = lazyWithRetry(() => import('./pages/AdminPartnerCommission')); +const AdminPartnerRevoke = lazyWithRetry(() => import('./pages/AdminPartnerRevoke')); +const AdminPartnerCampaignAssign = lazyWithRetry( + () => import('./pages/AdminPartnerCampaignAssign'), +); +const AdminWithdrawals = lazyWithRetry(() => import('./pages/AdminWithdrawals')); +const AdminWithdrawalDetail = lazyWithRetry(() => import('./pages/AdminWithdrawalDetail')); +const AdminWithdrawalReject = lazyWithRetry(() => import('./pages/AdminWithdrawalReject')); +const ReferralPartnerApply = lazyWithRetry(() => import('./pages/ReferralPartnerApply')); +const ReferralWithdrawalRequest = lazyWithRetry(() => import('./pages/ReferralWithdrawalRequest')); +const AdminUsers = lazyWithRetry(() => import('./pages/AdminUsers')); +const AdminPayments = lazyWithRetry(() => import('./pages/AdminPayments')); +const AdminPaymentMethods = lazyWithRetry(() => import('./pages/AdminPaymentMethods')); +const AdminPaymentMethodEdit = lazyWithRetry(() => import('./pages/AdminPaymentMethodEdit')); +const AdminPromoOffers = lazyWithRetry(() => import('./pages/AdminPromoOffers')); +const AdminPromoOfferTemplateEdit = lazyWithRetry( + () => import('./pages/AdminPromoOfferTemplateEdit'), +); +const AdminPromoOfferSend = lazyWithRetry(() => import('./pages/AdminPromoOfferSend')); +const AdminRemnawave = lazyWithRetry(() => import('./pages/AdminRemnawave')); +const AdminRemnawaveSquadDetail = lazyWithRetry(() => import('./pages/AdminRemnawaveSquadDetail')); +const AdminEmailTemplates = lazyWithRetry(() => import('./pages/AdminEmailTemplates')); +const AdminTrafficUsage = lazyWithRetry(() => import('./pages/AdminTrafficUsage')); +const AdminSalesStats = lazyWithRetry(() => import('./pages/AdminSalesStats')); +const AdminUpdates = lazyWithRetry(() => import('./pages/AdminUpdates')); +const AdminUserDetail = lazyWithRetry(() => import('./pages/AdminUserDetail')); +const AdminBroadcastDetail = lazyWithRetry(() => import('./pages/AdminBroadcastDetail')); +const AdminPinnedMessages = lazyWithRetry(() => import('./pages/AdminPinnedMessages')); +const AdminPinnedMessageCreate = lazyWithRetry(() => import('./pages/AdminPinnedMessageCreate')); +const AdminChannelSubscriptions = lazyWithRetry(() => import('./pages/AdminChannelSubscriptions')); +const AdminEmailTemplatePreview = lazyWithRetry(() => import('./pages/AdminEmailTemplatePreview')); +const AdminRoles = lazyWithRetry(() => import('./pages/AdminRoles')); +const AdminRoleEdit = lazyWithRetry(() => import('./pages/AdminRoleEdit')); +const AdminRoleAssign = lazyWithRetry(() => import('./pages/AdminRoleAssign')); +const AdminPolicies = lazyWithRetry(() => import('./pages/AdminPolicies')); +const AdminPolicyEdit = lazyWithRetry(() => import('./pages/AdminPolicyEdit')); +const AdminAuditLog = lazyWithRetry(() => import('./pages/AdminAuditLog')); +const AdminLandings = lazyWithRetry(() => import('./pages/AdminLandings')); +const AdminLandingEditor = lazyWithRetry(() => import('./pages/AdminLandingEditor')); +const AdminLandingStats = lazyWithRetry(() => import('./pages/AdminLandingStats')); +const AdminReferralNetwork = lazyWithRetry(() => import('./pages/ReferralNetwork')); // News pages -const NewsArticlePage = lazy(() => import('./pages/NewsArticle')); -const AdminNews = lazy(() => import('./pages/AdminNews')); -const AdminNewsCreate = lazy(() => import('./pages/AdminNewsCreate')); +const NewsArticlePage = lazyWithRetry(() => import('./pages/NewsArticle')); +const AdminNews = lazyWithRetry(() => import('./pages/AdminNews')); +const AdminNewsCreate = lazyWithRetry(() => import('./pages/AdminNewsCreate')); function ProtectedRoute({ children, @@ -190,6 +215,12 @@ function BlockingOverlay() { return null; } +/** Redirect /subscription/:id → /subscriptions/:id preserving the param */ +function LegacySubscriptionRedirect() { + const { subscriptionId } = useParams<{ subscriptionId: string }>(); + return ; +} + function App() { useAnalyticsCounters(); @@ -258,7 +289,17 @@ function App() { } /> + + + + + } + /> + @@ -267,6 +308,26 @@ function App() { } /> + + + + + + } + /> + {/* Legacy redirects for backward compatibility */} + } /> + + + + } + /> | null; country_code?: string; } diff --git a/src/api/adminRemnawave.ts b/src/api/adminRemnawave.ts index ab6e5fa..0218421 100644 --- a/src/api/adminRemnawave.ts +++ b/src/api/adminRemnawave.ts @@ -29,11 +29,9 @@ export interface SystemSummary { 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; } @@ -78,22 +76,60 @@ export interface NodeInfo { is_disabled: boolean; is_node_online: boolean; is_xray_running: boolean; - users_online?: number; + users_online: number; traffic_used_bytes?: number; traffic_limit_bytes?: number; last_status_change?: string; last_status_message?: string; - xray_uptime?: string; + xray_uptime: number; 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; + versions?: { xray: string; node: string } | null; + system?: { + info: { + arch: string; + cpus: number; + cpuModel: string; + memoryTotal: number; + hostname: string; + platform: string; + release: string; + type: string; + version: string; + networkInterfaces: string[]; + }; + stats: { + memoryFree: number; + memoryUsed: number; + uptime: number; + loadAvg: number[]; + interface?: { + interface: string; + rxBytesPerSec: number; + txBytesPerSec: number; + rxTotal: number; + txTotal: number; + } | null; + }; + } | null; + active_plugin_uuid?: string; + config_profile?: { + active_config_profile_uuid: string | null; + active_inbounds: Array<{ + uuid: string; + profile_uuid: string; + tag: string; + type: string; + network: string | null; + security: string | null; + port: number | null; + }>; + }; } export interface NodesListResponse { @@ -123,6 +159,27 @@ export interface NodeActionResponse { is_disabled?: boolean; } +// Realtime Traffic +export interface InboundTraffic { + tag: string; + downloadBytes: number; + uploadBytes: number; + totalBytes: number; +} + +export interface NodeRealtimeStats { + nodeUuid: string; + nodeName: string; + countryEmoji?: string; + providerName?: string; + downloadBytes: number; + uploadBytes: number; + totalBytes: number; + usersOnline: number; + inbounds?: InboundTraffic[]; + outbounds?: InboundTraffic[]; +} + // Squads export interface SquadWithLocalInfo { uuid: string; @@ -246,7 +303,7 @@ export const adminRemnawaveApi = { return response.data; }, - getNodesRealtime: async (): Promise[]> => { + getNodesRealtime: async (): Promise => { const response = await apiClient.get('/cabinet/admin/remnawave/nodes/realtime'); return response.data; }, diff --git a/src/api/adminUsers.ts b/src/api/adminUsers.ts index 294bfcf..3a0a23a 100644 --- a/src/api/adminUsers.ts +++ b/src/api/adminUsers.ts @@ -103,6 +103,7 @@ export interface UserDetailResponse { last_activity: string | null; cabinet_last_login: string | null; subscription: UserSubscriptionInfo | null; + subscriptions: UserSubscriptionInfo[]; promo_group: UserPromoGroupInfo | null; referral: UserReferralInfo; total_spent_kopeks: number; @@ -250,6 +251,8 @@ export interface PanelSyncStatusResponse { user_id: number; telegram_id: number; remnawave_uuid: string | null; + subscription_id: number | null; + subscription_tariff_name: string | null; last_sync: string | null; bot_subscription_status: string | null; bot_subscription_end_date: string | null; @@ -296,6 +299,7 @@ export interface UpdateSubscriptionRequest { | 'remove_traffic' | 'set_device_limit' | 'shorten'; + subscription_id?: number; days?: number; end_date?: string; tariff_id?: number; @@ -532,6 +536,34 @@ export const adminUsersApi = { return response.data; }, + // Assign a referrer to this user + assignReferrer: async ( + userId: number, + referrerId: number, + ): Promise<{ success: boolean; message: string }> => { + const response = await apiClient.post(`/cabinet/admin/users/${userId}/assign-referrer`, { + referrer_id: referrerId, + }); + return response.data; + }, + + // Remove this user's referrer + removeReferrer: async (userId: number): Promise<{ success: boolean; message: string }> => { + const response = await apiClient.delete(`/cabinet/admin/users/${userId}/referrer`); + return response.data; + }, + + // Remove a specific referral from this user's list + removeReferral: async ( + userId: number, + referralUserId: number, + ): Promise<{ success: boolean; message: string }> => { + const response = await apiClient.delete( + `/cabinet/admin/users/${userId}/referrals/${referralUserId}`, + ); + return response.data; + }, + // Get referrals getReferrals: async (userId: number, offset = 0, limit = 50): Promise => { const response = await apiClient.get(`/cabinet/admin/users/${userId}/referrals`, { @@ -559,8 +591,12 @@ export const adminUsersApi = { }, // Sync status - getSyncStatus: async (userId: number): Promise => { - const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`); + getSyncStatus: async ( + userId: number, + subscriptionId?: number, + ): Promise => { + const params = subscriptionId != null ? { subscription_id: subscriptionId } : undefined; + const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`, { params }); return response.data; }, @@ -568,8 +604,12 @@ export const adminUsersApi = { syncFromPanel: async ( userId: number, data: SyncFromPanelRequest = {}, + subscriptionId?: number, ): Promise => { - const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/from-panel`, data); + const params = subscriptionId != null ? { subscription_id: subscriptionId } : undefined; + const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/from-panel`, data, { + params, + }); return response.data; }, @@ -577,8 +617,12 @@ export const adminUsersApi = { syncToPanel: async ( userId: number, data: SyncToPanelRequest = {}, + subscriptionId?: number, ): Promise => { - const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/to-panel`, data); + const params = subscriptionId != null ? { subscription_id: subscriptionId } : undefined; + const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/to-panel`, data, { + params, + }); return response.data; }, @@ -601,26 +645,33 @@ export const adminUsersApi = { }, // Get panel info - getPanelInfo: async (userId: number): Promise => { - const response = await apiClient.get(`/cabinet/admin/users/${userId}/panel-info`); + getPanelInfo: async (userId: number, subscriptionId?: number): Promise => { + const response = await apiClient.get(`/cabinet/admin/users/${userId}/panel-info`, { + params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined, + }); return response.data; }, // Get node usage (always 30 days with daily breakdown) - getNodeUsage: async (userId: number): Promise => { - const response = await apiClient.get(`/cabinet/admin/users/${userId}/node-usage`); + getNodeUsage: async (userId: number, subscriptionId?: number): Promise => { + const response = await apiClient.get(`/cabinet/admin/users/${userId}/node-usage`, { + params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined, + }); return response.data; }, // Get user devices getUserDevices: async ( userId: number, + subscriptionId?: number, ): Promise<{ devices: { hwid: string; platform: string; device_model: string; created_at: string | null }[]; total: number; device_limit: number; }> => { - const response = await apiClient.get(`/cabinet/admin/users/${userId}/devices`); + const response = await apiClient.get(`/cabinet/admin/users/${userId}/devices`, { + params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined, + }); return response.data; }, @@ -628,16 +679,22 @@ export const adminUsersApi = { deleteUserDevice: async ( userId: number, hwid: string, + subscriptionId?: number, ): Promise<{ success: boolean; message: string; deleted_hwid: string | null }> => { - const response = await apiClient.delete(`/cabinet/admin/users/${userId}/devices/${hwid}`); + const response = await apiClient.delete(`/cabinet/admin/users/${userId}/devices/${hwid}`, { + params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined, + }); return response.data; }, // Reset all devices resetUserDevices: async ( userId: number, + subscriptionId?: number, ): Promise<{ success: boolean; message: string; deleted_count: number }> => { - const response = await apiClient.delete(`/cabinet/admin/users/${userId}/devices`); + const response = await apiClient.delete(`/cabinet/admin/users/${userId}/devices`, { + params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined, + }); return response.data; }, diff --git a/src/api/auth.ts b/src/api/auth.ts index 297fb77..4eae2be 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -78,7 +78,12 @@ export const authApi = { registerEmail: async ( email: string, password: string, - ): Promise<{ message: string; email: string }> => { + ): Promise<{ + message: string; + email?: string; + merge_required?: boolean; + merge_token?: string; + }> => { const response = await apiClient.post('/cabinet/auth/email/register', { email, password, diff --git a/src/api/balance.ts b/src/api/balance.ts index 49f97f7..8ae249f 100644 --- a/src/api/balance.ts +++ b/src/api/balance.ts @@ -1,4 +1,5 @@ import apiClient from './client'; +import i18n from '../i18n'; import type { Balance, Transaction, @@ -54,6 +55,7 @@ export const balanceApi = { amount_kopeks: number; payment_method: string; payment_option?: string; + language?: string; } = { amount_kopeks: amountKopeks, payment_method: paymentMethod, @@ -61,6 +63,7 @@ export const balanceApi = { if (paymentOption) { payload.payment_option = paymentOption; } + payload.language = i18n.language || 'ru'; const response = await apiClient.post('/cabinet/balance/topup', payload); return response.data; }, @@ -68,14 +71,21 @@ export const balanceApi = { // Activate promo code activatePromocode: async ( code: string, + subscriptionId?: number, ): Promise<{ success: boolean; - message: string; - balance_before: number; - balance_after: number; - bonus_description: string | null; + message?: string; + balance_before?: number; + balance_after?: number; + bonus_description?: string | null; + error?: string; + eligible_subscriptions?: Array<{ id: number; tariff_name: string; days_left: number }>; + code?: string; }> => { - const response = await apiClient.post('/cabinet/promocode/activate', { code }); + const response = await apiClient.post('/cabinet/promocode/activate', { + code, + ...(subscriptionId ? { subscription_id: subscriptionId } : {}), + }); return response.data; }, diff --git a/src/api/landings.ts b/src/api/landings.ts index d6cdb8a..83437c5 100644 --- a/src/api/landings.ts +++ b/src/api/landings.ts @@ -101,6 +101,7 @@ export interface PurchaseRequest { gift_recipient_type?: 'email' | 'telegram'; gift_recipient_value?: string; gift_message?: string; + language?: string; } export interface PurchaseResponse { diff --git a/src/api/promocodes.ts b/src/api/promocodes.ts index cde1307..32e56b7 100644 --- a/src/api/promocodes.ts +++ b/src/api/promocodes.ts @@ -25,6 +25,8 @@ export interface PromoCode { valid_from: string; valid_until: string | null; promo_group_id: number | null; + tariff_id: number | null; + tariff_name: string | null; created_by: number | null; created_at: string; updated_at: string; @@ -63,6 +65,7 @@ export interface PromoCodeCreateRequest { is_active?: boolean; first_purchase_only?: boolean; promo_group_id?: number | null; + tariff_id?: number | null; } export interface PromoCodeUpdateRequest { @@ -76,6 +79,7 @@ export interface PromoCodeUpdateRequest { is_active?: boolean; first_purchase_only?: boolean; promo_group_id?: number | null; + tariff_id?: number | null; } // ============== PromoGroup Types ============== diff --git a/src/api/subscription.ts b/src/api/subscription.ts index c1f676b..529e934 100644 --- a/src/api/subscription.ts +++ b/src/api/subscription.ts @@ -2,6 +2,8 @@ import apiClient from './client'; import type { Subscription, SubscriptionStatusResponse, + SubscriptionListItem, + SubscriptionsListResponse, RenewalOption, TrafficPackage, TrialInfo, @@ -11,50 +13,162 @@ import type { AppConfig, } from '../types'; +/** Helper: build query params with optional subscription_id */ +const withSubId = (subscriptionId?: number, extra?: Record) => ({ + params: { + ...(subscriptionId != null && { subscription_id: subscriptionId }), + ...extra, + }, +}); + +/** + * Helper for POST/PATCH/PUT: returns [body, axiosConfig] tuple. + * subscription_id goes as query param (backend expects Query(...)), NOT in body. + */ +const bodyWithSubId = ( + body: Record, + subscriptionId?: number, +): [Record, { params?: Record }] => [ + body, + subscriptionId != null ? { params: { subscription_id: subscriptionId } } : {}, +]; + export const subscriptionApi = { - getSubscription: async (): Promise => { - const response = await apiClient.get('/cabinet/subscription'); + // ── Multi-tariff endpoints ────────────────────────────────────────── + + getSubscriptions: async (): Promise => { + const response = await apiClient.get('/cabinet/subscriptions'); return response.data; }, - getRenewalOptions: async (): Promise => { - const response = await apiClient.get('/cabinet/subscription/renewal-options'); + getSubscriptionById: async (subscriptionId: number): Promise => { + const response = await apiClient.get( + `/cabinet/subscriptions/${subscriptionId}`, + ); + return response.data; + }, + + deleteSubscription: async (subscriptionId: number): Promise<{ message: string }> => { + const response = await apiClient.delete(`/cabinet/subscriptions/${subscriptionId}`); + return response.data; + }, + + // ── Legacy single-subscription status ─────────────────────────────── + + getSubscription: async (subscriptionId?: number): Promise => { + const response = await apiClient.get( + '/cabinet/subscription', + withSubId(subscriptionId), + ); + return response.data; + }, + + // ── Renewal ───────────────────────────────────────────────────────── + + getRenewalOptions: async (subscriptionId?: number): Promise => { + const response = await apiClient.get( + '/cabinet/subscription/renewal-options', + withSubId(subscriptionId), + ); return response.data; }, renewSubscription: async ( periodDays: number, + subscriptionId?: number, ): Promise<{ message: string; new_end_date: string; amount_paid_kopeks: number; }> => { - const response = await apiClient.post('/cabinet/subscription/renew', { - period_days: periodDays, - }); + const response = await apiClient.post( + '/cabinet/subscription/renew', + ...bodyWithSubId({ period_days: periodDays }, subscriptionId), + ); return response.data; }, - getTrafficPackages: async (): Promise => { + // ── Traffic ───────────────────────────────────────────────────────── + + getTrafficPackages: async (subscriptionId?: number): Promise => { const response = await apiClient.get( '/cabinet/subscription/traffic-packages', + withSubId(subscriptionId), ); return response.data; }, purchaseTraffic: async ( gb: number, + subscriptionId?: number, ): Promise<{ message: string; gb_added: number; amount_paid_kopeks: number; }> => { - const response = await apiClient.post('/cabinet/subscription/traffic', { gb }); + const response = await apiClient.post( + '/cabinet/subscription/traffic', + ...bodyWithSubId({ gb }, subscriptionId), + ); return response.data; }, + switchTraffic: async ( + gb: number, + subscriptionId?: number, + ): Promise<{ + success: boolean; + message: string; + old_traffic_gb: number; + new_traffic_gb: number; + charged_kopeks: number; + balance_kopeks: number; + balance_label: string; + }> => { + const response = await apiClient.put( + '/cabinet/subscription/traffic', + ...bodyWithSubId({ gb }, subscriptionId), + ); + return response.data; + }, + + saveTrafficCart: async (trafficGb: number, subscriptionId?: number): Promise => { + await apiClient.post( + '/cabinet/subscription/traffic/save-cart', + ...bodyWithSubId({ gb: trafficGb }, subscriptionId), + ); + }, + + refreshTraffic: async ( + subscriptionId?: number, + ): Promise<{ + success: boolean; + cached: boolean; + rate_limited?: boolean; + retry_after_seconds?: number; + source?: string; + traffic_used_bytes: number; + traffic_used_gb: number; + traffic_limit_bytes: number; + traffic_limit_gb: number; + traffic_used_percent: number; + is_unlimited: boolean; + lifetime_used_bytes?: number; + lifetime_used_gb?: number; + }> => { + const response = await apiClient.post( + '/cabinet/subscription/refresh-traffic', + {}, + withSubId(subscriptionId), + ); + return response.data; + }, + + // ── Devices ───────────────────────────────────────────────────────── + purchaseDevices: async ( devices: number, + subscriptionId?: number, ): Promise<{ success: boolean; message: string; @@ -65,12 +179,16 @@ export const subscriptionApi = { balance_kopeks: number; balance_label: string; }> => { - const response = await apiClient.post('/cabinet/subscription/devices/purchase', { devices }); + const response = await apiClient.post( + '/cabinet/subscription/devices/purchase', + ...bodyWithSubId({ devices }, subscriptionId), + ); return response.data; }, getDevicePrice: async ( devices: number = 1, + subscriptionId?: number, ): Promise<{ available: boolean; reason?: string; @@ -84,23 +202,30 @@ export const subscriptionApi = { can_add?: number; days_left?: number; base_device_price_kopeks?: number; - // Discount fields (from promo group) original_price_per_device_kopeks?: number; base_total_price_kopeks?: number; discount_percent?: number; discount_kopeks?: number; }> => { const response = await apiClient.get('/cabinet/subscription/devices/price', { - params: { devices }, + params: { + devices, + ...(subscriptionId != null && { subscription_id: subscriptionId }), + }, }); return response.data; }, - saveDevicesCart: async (devices: number): Promise => { - await apiClient.post('/cabinet/subscription/devices/save-cart', { devices }); + saveDevicesCart: async (devices: number, subscriptionId?: number): Promise => { + await apiClient.post( + '/cabinet/subscription/devices/save-cart', + ...bodyWithSubId({ devices }, subscriptionId), + ); }, - getDeviceReductionInfo: async (): Promise<{ + getDeviceReductionInfo: async ( + subscriptionId?: number, + ): Promise<{ available: boolean; reason?: string; current_device_limit: number; @@ -108,43 +233,98 @@ export const subscriptionApi = { can_reduce: number; connected_devices_count: number; }> => { - const response = await apiClient.get('/cabinet/subscription/devices/reduction-info'); + const response = await apiClient.get( + '/cabinet/subscription/devices/reduction-info', + withSubId(subscriptionId), + ); return response.data; }, reduceDevices: async ( newDeviceLimit: number, + subscriptionId?: number, ): Promise<{ success: boolean; message: string; old_device_limit: number; new_device_limit: number; }> => { - const response = await apiClient.post('/cabinet/subscription/devices/reduce', { - new_device_limit: newDeviceLimit, - }); + const response = await apiClient.post( + '/cabinet/subscription/devices/reduce', + ...bodyWithSubId({ new_device_limit: newDeviceLimit }, subscriptionId), + ); return response.data; }, - saveTrafficCart: async (trafficGb: number): Promise => { - await apiClient.post('/cabinet/subscription/traffic/save-cart', { gb: trafficGb }); + getDevices: async ( + subscriptionId?: number, + ): Promise<{ + devices: Array<{ + hwid: string; + platform: string; + device_model: string; + created_at: string | null; + }>; + total: number; + device_limit: number; + }> => { + const response = await apiClient.get( + '/cabinet/subscription/devices', + withSubId(subscriptionId), + ); + return response.data; }, + deleteDevice: async ( + hwid: string, + subscriptionId?: number, + ): Promise<{ + success: boolean; + message: string; + deleted_hwid: string; + }> => { + const response = await apiClient.delete( + `/cabinet/subscription/devices/${encodeURIComponent(hwid)}`, + withSubId(subscriptionId), + ); + return response.data; + }, + + deleteAllDevices: async ( + subscriptionId?: number, + ): Promise<{ + success: boolean; + message: string; + deleted_count: number; + }> => { + const response = await apiClient.delete( + '/cabinet/subscription/devices', + withSubId(subscriptionId), + ); + return response.data; + }, + + // ── Autopay ───────────────────────────────────────────────────────── + updateAutopay: async ( enabled: boolean, daysBefore?: number, + subscriptionId?: number, ): Promise<{ message: string; autopay_enabled: boolean; autopay_days_before: number; }> => { - const response = await apiClient.patch('/cabinet/subscription/autopay', { - enabled, - days_before: daysBefore, - }); + const response = await apiClient.patch( + '/cabinet/subscription/autopay', + { enabled, days_before: daysBefore }, + withSubId(subscriptionId), + ); return response.data; }, + // ── Trial ─────────────────────────────────────────────────────────── + getTrialInfo: async (): Promise => { const response = await apiClient.get('/cabinet/subscription/trial'); return response.data; @@ -155,32 +335,40 @@ export const subscriptionApi = { return response.data; }, - getPurchaseOptions: async (): Promise => { - const response = await apiClient.get('/cabinet/subscription/purchase-options'); + // ── Purchase ──────────────────────────────────────────────────────── + + getPurchaseOptions: async (subscriptionId?: number): Promise => { + const response = await apiClient.get( + '/cabinet/subscription/purchase-options', + withSubId(subscriptionId), + ); return response.data; }, - previewPurchase: async (selection: PurchaseSelection): Promise => { + previewPurchase: async ( + selection: PurchaseSelection, + subscriptionId?: number, + ): Promise => { const response = await apiClient.post( '/cabinet/subscription/purchase-preview', - { - selection, - }, + ...bodyWithSubId({ selection }, subscriptionId), ); return response.data; }, submitPurchase: async ( selection: PurchaseSelection, + subscriptionId?: number, ): Promise<{ success: boolean; message: string; subscription: Subscription; was_trial_conversion: boolean; }> => { - const response = await apiClient.post('/cabinet/subscription/purchase', { - selection, - }); + const response = await apiClient.post( + '/cabinet/subscription/purchase', + ...bodyWithSubId({ selection }, subscriptionId), + ); return response.data; }, @@ -205,12 +393,11 @@ export const subscriptionApi = { return response.data; }, - getAppConfig: async (): Promise => { - const response = await apiClient.get('/cabinet/subscription/app-config'); - return response.data; - }, + // ── Countries / Servers ───────────────────────────────────────────── - getCountries: async (): Promise<{ + getCountries: async ( + subscriptionId?: number, + ): Promise<{ countries: Array<{ uuid: string; name: string; @@ -229,12 +416,16 @@ export const subscriptionApi = { days_left: number; discount_percent: number; }> => { - const response = await apiClient.get('/cabinet/subscription/countries'); + const response = await apiClient.get( + '/cabinet/subscription/countries', + withSubId(subscriptionId), + ); return response.data; }, updateCountries: async ( countries: string[], + subscriptionId?: number, ): Promise<{ message: string; added: string[]; @@ -242,11 +433,18 @@ export const subscriptionApi = { amount_paid_kopeks: number; connected_squads: string[]; }> => { - const response = await apiClient.post('/cabinet/subscription/countries', { countries }); + const response = await apiClient.post( + '/cabinet/subscription/countries', + ...bodyWithSubId({ countries }, subscriptionId), + ); return response.data; }, - getConnectionLink: async (): Promise<{ + // ── Connection ────────────────────────────────────────────────────── + + getConnectionLink: async ( + subscriptionId?: number, + ): Promise<{ subscription_url: string | null; display_link: string | null; happ_redirect_link: string | null; @@ -257,7 +455,10 @@ export const subscriptionApi = { steps: string[]; }; }> => { - const response = await apiClient.get('/cabinet/subscription/connection-link'); + const response = await apiClient.get( + '/cabinet/subscription/connection-link', + withSubId(subscriptionId), + ); return response.data; }, @@ -276,44 +477,19 @@ export const subscriptionApi = { return response.data; }, - getDevices: async (): Promise<{ - devices: Array<{ - hwid: string; - platform: string; - device_model: string; - created_at: string | null; - }>; - total: number; - device_limit: number; - }> => { - const response = await apiClient.get('/cabinet/subscription/devices'); - return response.data; - }, - - deleteDevice: async ( - hwid: string, - ): Promise<{ - success: boolean; - message: string; - deleted_hwid: string; - }> => { - const response = await apiClient.delete( - `/cabinet/subscription/devices/${encodeURIComponent(hwid)}`, + getAppConfig: async (subscriptionId?: number): Promise => { + const response = await apiClient.get( + '/cabinet/subscription/app-config', + withSubId(subscriptionId), ); return response.data; }, - deleteAllDevices: async (): Promise<{ - success: boolean; - message: string; - deleted_count: number; - }> => { - const response = await apiClient.delete('/cabinet/subscription/devices'); - return response.data; - }, + // ── Tariff switch ─────────────────────────────────────────────────── previewTariffSwitch: async ( tariffId: number, + subscriptionId?: number, ): Promise<{ can_switch: boolean; current_tariff_id: number | null; @@ -329,20 +505,20 @@ export const subscriptionApi = { missing_amount_kopeks: number; missing_amount_label: string; is_upgrade: boolean; - // Discount fields (from promo group) base_upgrade_cost_kopeks?: number; discount_percent?: number; discount_kopeks?: number; }> => { - const response = await apiClient.post('/cabinet/subscription/tariff/switch/preview', { - tariff_id: tariffId, - period_days: 30, // Default period for switch - }); + const response = await apiClient.post( + '/cabinet/subscription/tariff/switch/preview', + ...bodyWithSubId({ tariff_id: tariffId, period_days: 30 }, subscriptionId), + ); return response.data; }, switchTariff: async ( tariffId: number, + subscriptionId?: number, ): Promise<{ success: boolean; message: string; @@ -354,56 +530,29 @@ export const subscriptionApi = { balance_kopeks: number; balance_label: string; }> => { - const response = await apiClient.post('/cabinet/subscription/tariff/switch', { - tariff_id: tariffId, - period_days: 30, - }); + const response = await apiClient.post( + '/cabinet/subscription/tariff/switch', + ...bodyWithSubId({ tariff_id: tariffId, period_days: 30 }, subscriptionId), + ); return response.data; }, - togglePause: async (): Promise<{ + // ── Daily subscription ────────────────────────────────────────────── + + togglePause: async ( + subscriptionId?: number, + ): Promise<{ success: boolean; message: string; is_paused: boolean; balance_kopeks: number; balance_label: string; }> => { - const response = await apiClient.post('/cabinet/subscription/pause'); - return response.data; - }, - - switchTraffic: async ( - gb: number, - ): Promise<{ - success: boolean; - message: string; - old_traffic_gb: number; - new_traffic_gb: number; - charged_kopeks: number; - balance_kopeks: number; - balance_label: string; - }> => { - const response = await apiClient.put('/cabinet/subscription/traffic', { gb }); - return response.data; - }, - - // Refresh traffic usage from RemnaWave (rate limited: 1 per 60 seconds) - refreshTraffic: async (): Promise<{ - success: boolean; - cached: boolean; - rate_limited?: boolean; - retry_after_seconds?: number; - source?: string; - traffic_used_bytes: number; - traffic_used_gb: number; - traffic_limit_bytes: number; - traffic_limit_gb: number; - traffic_used_percent: number; - is_unlimited: boolean; - lifetime_used_bytes?: number; - lifetime_used_gb?: number; - }> => { - const response = await apiClient.post('/cabinet/subscription/refresh-traffic'); + const response = await apiClient.post( + '/cabinet/subscription/pause', + undefined, + withSubId(subscriptionId), + ); return response.data; }, }; diff --git a/src/api/tariffs.ts b/src/api/tariffs.ts index d3bc1cb..48d0c1c 100644 --- a/src/api/tariffs.ts +++ b/src/api/tariffs.ts @@ -86,7 +86,7 @@ export interface TariffDetail { is_daily: boolean; daily_price_kopeks: number; // Режим сброса трафика - traffic_reset_mode: string | null; // 'DAY', 'WEEK', 'MONTH', 'NO_RESET', null = глобальная настройка + traffic_reset_mode: string | null; // 'DAY', 'WEEK', 'MONTH', 'MONTH_ROLLING', 'NO_RESET', null = глобальная настройка // Внешний сквад RemnaWave external_squad_uuid: string | null; created_at: string; diff --git a/src/api/wheel.ts b/src/api/wheel.ts index d8bac9b..dd47f82 100644 --- a/src/api/wheel.ts +++ b/src/api/wheel.ts @@ -8,6 +8,12 @@ export interface WheelPrize { prize_type: string; } +export interface EligibleSubscription { + id: number; + tariff_name: string | null; + days_left: number; +} + export interface WheelConfig { is_enabled: boolean; name: string; @@ -25,6 +31,7 @@ export interface WheelConfig { user_balance_kopeks: number; required_balance_kopeks: number; has_subscription: boolean; + eligible_subscriptions: EligibleSubscription[] | null; } export interface SpinAvailability { @@ -187,9 +194,13 @@ export const wheelApi = { return response.data; }, - spin: async (paymentType: 'telegram_stars' | 'subscription_days'): Promise => { + spin: async ( + paymentType: 'telegram_stars' | 'subscription_days', + subscriptionId?: number, + ): Promise => { const response = await apiClient.post('/cabinet/wheel/spin', { payment_type: paymentType, + ...(subscriptionId != null && { subscription_id: subscriptionId }), }); return response.data; }, diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index aa4e47a..0285597 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -11,6 +11,18 @@ interface ErrorBoundaryState { error: Error | null; } +function isChunkLoadError(error: Error): boolean { + const msg = error.message || ''; + return ( + msg.includes('dynamically imported module') || + msg.includes('Importing a module script failed') || + msg.includes('Failed to fetch dynamically imported module') || + msg.includes('Loading chunk') || + msg.includes('ChunkLoadError') || + error.name === 'ChunkLoadError' + ); +} + export class ErrorBoundary extends Component { constructor(props: ErrorBoundaryProps) { super(props); @@ -23,6 +35,19 @@ export class ErrorBoundary extends Component 30_000) { + sessionStorage.setItem(reloadKey, String(now)); + window.location.reload(); + return; + } + } } handleReset = () => { @@ -36,6 +61,7 @@ export class ErrorBoundary extends Component⚠️

Something went wrong

- {this.state.error?.message || 'An unexpected error occurred'} + {isChunk + ? 'App was updated. Reloading...' + : this.state.error?.message || 'An unexpected error occurred'}

diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx index 042b7bd..981a8cd 100644 --- a/src/components/LanguageSwitcher.tsx +++ b/src/components/LanguageSwitcher.tsx @@ -1,19 +1,27 @@ import { useTranslation } from 'react-i18next'; import { useState, useRef, useEffect } from 'react'; - -const languages = [ - { code: 'ru', name: 'RU', flag: '🇷🇺', fullName: 'Русский' }, - { code: 'en', name: 'EN', flag: '🇬🇧', fullName: 'English' }, - { code: 'zh', name: 'ZH', flag: '🇨🇳', fullName: '中文' }, - { code: 'fa', name: 'FA', flag: '🇮🇷', fullName: 'فارسی' }, -]; +import { infoApi, type LanguageInfo } from '@/api/info'; export default function LanguageSwitcher() { const { i18n } = useTranslation(); const [isOpen, setIsOpen] = useState(false); + const [availableLanguages, setAvailableLanguages] = useState([]); const dropdownRef = useRef(null); - const currentLang = languages.find((l) => l.code === i18n.language) || languages[0]; + useEffect(() => { + const fetchLanguages = async () => { + try { + const data = await infoApi.getLanguages(); + setAvailableLanguages(data.languages); + } catch { + // Silently fall back to empty list — component handles it gracefully + } + }; + fetchLanguages(); + }, []); + + const currentLang = availableLanguages.find((l) => l.code === i18n.language) || + availableLanguages[0] || { code: 'ru', name: 'RU', flag: '🇷🇺' }; useEffect(() => { function handleClickOutside(event: MouseEvent) { @@ -27,16 +35,18 @@ export default function LanguageSwitcher() { const changeLanguage = (code: string) => { i18n.changeLanguage(code); - // Set document direction for RTL languages document.documentElement.dir = code === 'fa' ? 'rtl' : 'ltr'; setIsOpen(false); }; - // Set initial direction on mount useEffect(() => { document.documentElement.dir = i18n.language === 'fa' ? 'rtl' : 'ltr'; }, [i18n.language]); + if (availableLanguages.length <= 1) { + return null; + } + return (
))}
diff --git a/src/components/PromoOffersSection.tsx b/src/components/PromoOffersSection.tsx index 998ccb3..63c4d1a 100644 --- a/src/components/PromoOffersSection.tsx +++ b/src/components/PromoOffersSection.tsx @@ -114,6 +114,7 @@ export default function PromoOffersSection({ className = '' }: PromoOffersSectio queryClient.invalidateQueries({ queryKey: ['promo-offers'] }); queryClient.invalidateQueries({ queryKey: ['active-discount'] }); queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); setSuccessMessage(result.message); diff --git a/src/components/SuccessNotificationModal.tsx b/src/components/SuccessNotificationModal.tsx index 08f1d74..1f43437 100644 --- a/src/components/SuccessNotificationModal.tsx +++ b/src/components/SuccessNotificationModal.tsx @@ -190,7 +190,7 @@ export default function SuccessNotificationModal() { const handleGoToSubscription = () => { hide(); - navigate('/subscription'); + navigate('/subscriptions'); }; const handleGoToBalance = () => { diff --git a/src/components/WebBackButton.tsx b/src/components/WebBackButton.tsx new file mode 100644 index 0000000..61c5555 --- /dev/null +++ b/src/components/WebBackButton.tsx @@ -0,0 +1,32 @@ +import { Link } from 'react-router'; +import { usePlatform } from '../platform'; +import { BackIcon } from './icons'; + +interface WebBackButtonProps { + to: string; + replace?: boolean; + className?: string; +} + +/** + * Back button visible only on web platform. + * Hidden in Telegram Mini App — native back button handles navigation there. + */ +export function WebBackButton({ to, replace, className }: WebBackButtonProps) { + const { platform } = usePlatform(); + + if (platform === 'telegram') return null; + + return ( + + + + ); +} diff --git a/src/components/WebSocketNotifications.tsx b/src/components/WebSocketNotifications.tsx index d411263..f8b38ea 100644 --- a/src/components/WebSocketNotifications.tsx +++ b/src/components/WebSocketNotifications.tsx @@ -82,7 +82,11 @@ export default function WebSocketNotifications() { expiresAt: message.expires_at, tariffName: message.tariff_name, }); - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); refreshUser(); @@ -96,7 +100,11 @@ export default function WebSocketNotifications() { amountKopeks: message.amount_kopeks, expiresAt: message.new_expires_at, }); - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); refreshUser(); @@ -115,7 +123,7 @@ export default function WebSocketNotifications() { }, ), icon: , - onClick: () => navigate('/subscription'), + onClick: () => navigate('/subscriptions'), duration: 10000, }); return; @@ -130,10 +138,14 @@ export default function WebSocketNotifications() { 'Your subscription has expired. Renew to continue using the service.', ), icon: 😢, - onClick: () => navigate('/subscription'), + onClick: () => navigate('/subscriptions'), duration: 10000, }); - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); refreshUser(); return; } @@ -171,7 +183,11 @@ export default function WebSocketNotifications() { icon: 🔄, duration: 5000, }); - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); return; } @@ -183,7 +199,11 @@ export default function WebSocketNotifications() { devicesAdded: message.devices_added, newDeviceLimit: message.new_device_limit, }); - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); queryClient.invalidateQueries({ queryKey: ['transactions'] }); @@ -199,7 +219,11 @@ export default function WebSocketNotifications() { trafficGbAdded: message.traffic_gb_added, newTrafficLimitGb: message.new_traffic_limit_gb, }); - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); queryClient.invalidateQueries({ queryKey: ['transactions'] }); @@ -222,10 +246,14 @@ export default function WebSocketNotifications() { }, ), icon: 🔁, - onClick: () => navigate('/subscription'), + onClick: () => navigate('/subscriptions'), duration: 8000, }); - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); refreshUser(); @@ -240,7 +268,7 @@ export default function WebSocketNotifications() { message.reason || t('wsNotifications.autopay.failedMessage', 'Failed to auto-renew your subscription'), icon: , - onClick: () => navigate('/subscription'), + onClick: () => navigate('/subscriptions'), duration: 10000, }); return; diff --git a/src/components/admin/FavoritesTab.tsx b/src/components/admin/FavoritesTab.tsx index d7c223d..9f40bb9 100644 --- a/src/components/admin/FavoritesTab.tsx +++ b/src/components/admin/FavoritesTab.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { SettingDefinition, adminSettingsApi } from '../../api/adminSettings'; import { StarIcon } from './icons'; -import { SettingRow } from './SettingRow'; +import { SettingsTableRow } from './SettingsTableRow'; interface FavoritesTabProps { settings: SettingDefinition[]; @@ -42,9 +42,9 @@ export function FavoritesTab({ settings, isFavorite, toggleFavorite }: Favorites } return ( -
- {settings.map((setting) => ( - + {settings.map((setting, idx) => ( + resetSettingMutation.mutate(setting.key)} isUpdating={updateSettingMutation.isPending} isResetting={resetSettingMutation.isPending} + isLast={idx === settings.length - 1} /> ))}
diff --git a/src/components/admin/QuickToggles.tsx b/src/components/admin/QuickToggles.tsx new file mode 100644 index 0000000..649de9c --- /dev/null +++ b/src/components/admin/QuickToggles.tsx @@ -0,0 +1,70 @@ +import { useTranslation } from 'react-i18next'; +import { SettingDefinition } from '../../api/adminSettings'; +import { cn } from '../../lib/utils'; +import { formatSettingKey } from './utils'; + +interface QuickTogglesProps { + settings: SettingDefinition[]; + onUpdate: (key: string, value: string) => void; + disabled?: boolean; + className?: string; +} + +export function QuickToggles({ settings, onUpdate, disabled, className }: QuickTogglesProps) { + const { t } = useTranslation(); + + const booleanSettings = settings.filter((s) => s.type === 'bool' && !s.read_only); + + if (booleanSettings.length === 0) { + return null; + } + + return ( +
+ + {t('admin.settings.quickToggles')} + +
+ {booleanSettings.map((setting) => { + const isOn = setting.current === true || setting.current === 'true'; + const formattedKey = formatSettingKey(setting.name || setting.key); + const label = t(`admin.settings.settingNames.${formattedKey}`, formattedKey); + + return ( + + ); + })} +
+
+ ); +} diff --git a/src/components/admin/SettingInput.tsx b/src/components/admin/SettingInput.tsx index 42da472..aef267e 100644 --- a/src/components/admin/SettingInput.tsx +++ b/src/components/admin/SettingInput.tsx @@ -184,7 +184,7 @@ export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps) - return ( - - ); - })} + {/* Special item chips (branding, theme, analytics, buttons) */} + {specialItems.map((item) => { + const isActive = isSpecialActive(item.id); + return ( + + ); + })} + + {/* Group chips */} + {SETTINGS_TREE.groups.map((group) => { + const hasActiveChild = isGroupActive(group.id); + const isExpanded = expandedGroup === group.id; + return ( + + ); + })} + + + {/* Level 2: Sub-item chips (shown when a group is expanded) */} + {expandedGroup && ( +
+ {SETTINGS_TREE.groups + .find((g) => g.id === expandedGroup) + ?.children.map((child) => { + const isActive = activeSection === child.id; + return ( + + ); + })} + + )}
); } diff --git a/src/components/admin/SettingsSearch.tsx b/src/components/admin/SettingsSearch.tsx index 033d744..42fa5e8 100644 --- a/src/components/admin/SettingsSearch.tsx +++ b/src/components/admin/SettingsSearch.tsx @@ -208,7 +208,7 @@ export function SettingsSearchMobile({ }; return ( -
+
>(new Set()); - - const toggleSection = (key: string) => { - setExpandedSections((prev) => { - const next = new Set(prev); - if (next.has(key)) { - next.delete(key); - } else { - next.add(key); - } - return next; - }); - }; - const updateSettingMutation = useMutation({ mutationFn: ({ key, value }: { key: string; value: string }) => adminSettingsApi.updateSetting(key, value), @@ -58,92 +43,80 @@ export function SettingsTab({ }, }); - // If searching, show flat list - if (searchQuery) { + // Search mode: flat list of filtered results + if (searchQuery.trim()) { + if (filteredSettings.length === 0) { + return ( +
+

{t('admin.settings.noSettings')}

+
+ ); + } return ( -
- {filteredSettings.length === 0 ? ( -
-

{t('admin.settings.noSettings')}

-
- ) : ( -
- {filteredSettings.map((setting) => ( - toggleFavorite(setting.key)} - onUpdate={(value) => updateSettingMutation.mutate({ key: setting.key, value })} - onReset={() => resetSettingMutation.mutate(setting.key)} - isUpdating={updateSettingMutation.isPending} - isResetting={resetSettingMutation.isPending} - /> - ))} -
- )} +
+ {filteredSettings.map((setting, idx) => ( + toggleFavorite(setting.key)} + onUpdate={(value) => updateSettingMutation.mutate({ key: setting.key, value })} + onReset={() => resetSettingMutation.mutate(setting.key)} + isUpdating={updateSettingMutation.isPending} + isResetting={resetSettingMutation.isPending} + isLast={idx === filteredSettings.length - 1} + /> + ))}
); } - // Show accordion for subcategories - return ( -
- {categories.map((cat) => { - const isExpanded = expandedSections.has(cat.key); - return ( -
- {/* Accordion header */} - + // Normal mode: QuickToggles + settings by category + const allCategorySettings = categories.flatMap((c) => c.settings); - {/* Accordion content */} - {isExpanded && ( -
-
- {cat.settings.map((setting) => ( - toggleFavorite(setting.key)} - onUpdate={(value) => - updateSettingMutation.mutate({ key: setting.key, value }) - } - onReset={() => resetSettingMutation.mutate(setting.key)} - isUpdating={updateSettingMutation.isPending} - isResetting={resetSettingMutation.isPending} - /> - ))} -
+ if (allCategorySettings.length === 0) { + return ( +
+

{t('admin.settings.noSettings')}

+
+ ); + } + + return ( +
+ updateSettingMutation.mutate({ key, value })} + disabled={updateSettingMutation.isPending} + /> + {categories.map((category) => { + if (category.settings.length === 0) return null; + return ( +
+ {categories.length > 1 && ( +
+

{category.label}

+ {category.settings.length}
)} +
+ {category.settings.map((setting, idx) => ( + toggleFavorite(setting.key)} + onUpdate={(value) => updateSettingMutation.mutate({ key: setting.key, value })} + onReset={() => resetSettingMutation.mutate(setting.key)} + isUpdating={updateSettingMutation.isPending} + isResetting={resetSettingMutation.isPending} + isLast={idx === category.settings.length - 1} + /> + ))} +
); })} - - {categories.length === 0 && ( -
-

{t('admin.settings.noSettings')}

-
- )}
); } diff --git a/src/components/admin/SettingsTableRow.tsx b/src/components/admin/SettingsTableRow.tsx new file mode 100644 index 0000000..0218b8c --- /dev/null +++ b/src/components/admin/SettingsTableRow.tsx @@ -0,0 +1,179 @@ +import { useTranslation } from 'react-i18next'; +import { SettingDefinition } from '../../api/adminSettings'; +import { cn } from '../../lib/utils'; +import { StarIcon, LockIcon, RefreshIcon } from './icons'; +import { SettingInput } from './SettingInput'; +import { Toggle } from './Toggle'; +import { formatSettingKey, stripHtml } from './utils'; + +interface SettingsTableRowProps { + setting: SettingDefinition; + isFavorite: boolean; + onToggleFavorite: () => void; + onUpdate: (value: string) => void; + onReset: () => void; + isUpdating?: boolean; + isResetting?: boolean; + isLast?: boolean; + className?: string; +} + +export function SettingsTableRow({ + setting, + isFavorite, + onToggleFavorite, + onUpdate, + onReset, + isUpdating, + isResetting, + isLast, + className, +}: SettingsTableRowProps) { + const { t } = useTranslation(); + + const formattedKey = formatSettingKey(setting.name || setting.key); + const displayName = t(`admin.settings.settingNames.${formattedKey}`, formattedKey); + const description = setting.hint?.description ? stripHtml(setting.hint.description) : null; + const isModified = setting.has_override; + const isBool = setting.type === 'bool'; + const boolChecked = setting.current === true || setting.current === 'true'; + + const isLongValue = (() => { + const val = String(setting.current ?? ''); + const key = setting.key.toLowerCase(); + return ( + val.length > 50 || + val.includes('\n') || + val.startsWith('[') || + val.startsWith('{') || + key.includes('_items') || + key.includes('_config') || + key.includes('_keywords') || + key.includes('_template') || + key.includes('_packages') || + key.includes('_list') || + key.includes('_json') || + key.includes('_periods') || + key.includes('_discounts') + ); + })(); + + return ( +
+
+ {/* Left side: name, badges, key */} +
+ {/* Name + badges row */} +
+ {displayName} + + {isModified && ( + + {t('admin.settings.modified')} + + )} + + {setting.has_override && !setting.read_only && ( + + {t('admin.settings.badgeDb')} + + )} + + {setting.read_only && ( + + {t('admin.settings.badgeEnv')} + + + )} +
+ + {/* Setting key */} +
+ {setting.key} +
+ + {/* Description for long values */} + {isLongValue && description && ( +

{description}

+ )} +
+ + {/* Right side: control + action buttons */} +
+ {setting.read_only ? ( + + {isBool + ? boolChecked + ? t('admin.settings.enabled') + : t('admin.settings.disabled') + : String(setting.current ?? '-')} + + ) : isBool ? ( + onUpdate(boolChecked ? 'false' : 'true')} + disabled={isUpdating} + aria-label={displayName} + /> + ) : ( +
+ +
+ )} + + {/* Reset button -- hover-reveal when has_override */} + {isModified && !setting.read_only && ( + + )} + + {/* Favorite button -- visible if favorited, hover-reveal otherwise */} + +
+
+
+ ); +} diff --git a/src/components/admin/SettingsTreeSidebar.tsx b/src/components/admin/SettingsTreeSidebar.tsx new file mode 100644 index 0000000..8454bae --- /dev/null +++ b/src/components/admin/SettingsTreeSidebar.tsx @@ -0,0 +1,324 @@ +import { useState, useRef, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { SETTINGS_TREE } from './constants'; +import { StarIcon, SearchIcon, CloseIcon, ChevronDownIcon } from './icons'; +import { SettingDefinition } from '../../api/adminSettings'; +import { formatSettingKey } from './utils'; +import { cn } from '../../lib/utils'; + +interface SettingsTreeSidebarProps { + activeSection: string; + onSectionChange: (sectionId: string) => void; + favoritesCount: number; + searchQuery: string; + onSearchChange: (query: string) => void; + allSettings?: SettingDefinition[]; + onSelectSetting?: (setting: SettingDefinition) => void; + className?: string; +} + +export function SettingsTreeSidebar({ + activeSection, + onSectionChange, + favoritesCount, + searchQuery, + onSearchChange, + allSettings, + onSelectSetting, + className, +}: SettingsTreeSidebarProps) { + const { t } = useTranslation(); + const [expandedGroup, setExpandedGroup] = useState(null); + const [isSearchOpen, setIsSearchOpen] = useState(false); + const [highlightedIndex, setHighlightedIndex] = useState(0); + const searchContainerRef = useRef(null); + const inputRef = useRef(null); + + // Auto-expand the group containing the active section + useEffect(() => { + for (const group of SETTINGS_TREE.groups) { + if (group.children.some((child) => child.id === activeSection)) { + setExpandedGroup(group.id); + return; + } + } + }, [activeSection]); + + // Filter settings for autocomplete + const suggestions = + searchQuery.trim() && allSettings + ? allSettings + .filter((s) => { + const q = searchQuery.toLowerCase().trim(); + if (s.key.toLowerCase().includes(q)) return true; + if (s.name?.toLowerCase().includes(q)) return true; + const formattedKey = formatSettingKey(s.name || s.key); + const translatedName = t(`admin.settings.settingNames.${formattedKey}`, formattedKey); + if (translatedName.toLowerCase().includes(q)) return true; + if (s.hint?.description?.toLowerCase().includes(q)) return true; + const categoryLabel = t(`admin.settings.categories.${s.category.key}`, s.category.key); + if (categoryLabel.toLowerCase().includes(q)) return true; + return false; + }) + .slice(0, 8) + : []; + + // Close dropdown on click outside + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (searchContainerRef.current && !searchContainerRef.current.contains(e.target as Node)) { + setIsSearchOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + // Reset highlighted index when suggestions change + useEffect(() => { + setHighlightedIndex(0); + }, [suggestions.length, searchQuery]); + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (!isSearchOpen || suggestions.length === 0) return; + + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + setHighlightedIndex((i) => (i + 1) % suggestions.length); + break; + case 'ArrowUp': + e.preventDefault(); + setHighlightedIndex((i) => (i - 1 + suggestions.length) % suggestions.length); + break; + case 'Enter': + e.preventDefault(); + if (suggestions[highlightedIndex]) { + handleSelectSuggestion(suggestions[highlightedIndex]); + } + break; + case 'Escape': + setIsSearchOpen(false); + inputRef.current?.blur(); + break; + } + }; + + const handleSelectSuggestion = (setting: SettingDefinition) => { + setIsSearchOpen(false); + onSearchChange(setting.name || setting.key); + onSelectSetting?.(setting); + }; + + const getSettingDisplayName = (setting: SettingDefinition) => { + const formattedKey = formatSettingKey(setting.name || setting.key); + return t(`admin.settings.settingNames.${formattedKey}`, formattedKey); + }; + + const handleGroupToggle = (groupId: string) => { + if (expandedGroup === groupId) { + setExpandedGroup(null); + } else { + setExpandedGroup(groupId); + // Auto-select first child when expanding + const group = SETTINGS_TREE.groups.find((g) => g.id === groupId); + if (group && group.children.length > 0) { + onSectionChange(group.children[0].id); + } + } + }; + + const isGroupActive = (groupId: string) => { + const group = SETTINGS_TREE.groups.find((g) => g.id === groupId); + return group?.children.some((child) => child.id === activeSection) ?? false; + }; + + // Special items excluding favorites (favorites is rendered separately) + const customizationItems = SETTINGS_TREE.specialItems.filter((item) => item.id !== 'favorites'); + + return ( + + ); +} diff --git a/src/components/admin/constants.ts b/src/components/admin/constants.ts index 812f33f..356de38 100644 --- a/src/components/admin/constants.ts +++ b/src/components/admin/constants.ts @@ -1,111 +1,160 @@ import { ThemeColors, DEFAULT_THEME_COLORS } from '../../types/theme'; -// Menu item types -export interface MenuItem { +// Tree sidebar types +export interface TreeSubItem { id: string; + categories: string[]; +} + +export interface TreeGroup { + id: string; + icon: string; + children: TreeSubItem[]; +} + +export interface SpecialItem { + id: string; + icon?: string; iconType?: 'star' | null; - categories?: string[]; } -export interface MenuSection { - id: string; - items: MenuItem[]; +export interface SettingsTreeConfig { + specialItems: SpecialItem[]; + groups: TreeGroup[]; } -// Sidebar menu configuration -export const MENU_SECTIONS: MenuSection[] = [ - { - id: 'main', - items: [ - { id: 'favorites', iconType: 'star' }, - { id: 'branding', iconType: null }, - { id: 'theme', iconType: null }, - { id: 'analytics', iconType: null }, - { id: 'buttons', iconType: null }, - ], - }, - { - id: 'settings', - items: [ - { - id: 'payments', - iconType: null, - categories: [ - 'PAYMENT', - 'PAYMENT_VERIFICATION', - 'YOOKASSA', - 'CRYPTOBOT', - 'HELEKET', - 'PLATEGA', - 'TRIBUTE', - 'MULENPAY', - 'PAL24', - 'WATA', - 'TELEGRAM', - ], - }, - { - id: 'subscriptions', - iconType: null, - categories: [ - 'SUBSCRIPTIONS_CORE', - 'SIMPLE_SUBSCRIPTION', - 'PERIODS', - 'SUBSCRIPTION_PRICES', - 'TRAFFIC', - 'TRAFFIC_PACKAGES', - 'TRIAL', - 'AUTOPAY', - ], - }, - { - id: 'interface', - iconType: null, - categories: [ - 'INTERFACE', - 'INTERFACE_BRANDING', - 'INTERFACE_SUBSCRIPTION', - 'CONNECT_BUTTON', - 'MINIAPP', - 'TELEGRAM_WIDGET', - 'TELEGRAM_OIDC', - 'HAPP', - 'SKIP', - 'ADDITIONAL', - ], - }, - { - id: 'notifications', - iconType: null, - categories: ['NOTIFICATIONS', 'ADMIN_NOTIFICATIONS', 'ADMIN_REPORTS'], - }, - { id: 'database', iconType: null, categories: ['DATABASE', 'POSTGRES', 'SQLITE', 'REDIS'] }, - { - id: 'system', - iconType: null, - categories: [ - 'CORE', - 'REMNAWAVE', - 'SERVER_STATUS', - 'MONITORING', - 'MAINTENANCE', - 'BACKUP', - 'VERSION', - 'WEB_API', - 'WEBHOOK', - 'LOG', - 'DEBUG', - 'EXTERNAL_ADMIN', - ], - }, - { - id: 'users', - iconType: null, - categories: ['SUPPORT', 'LOCALIZATION', 'CHANNEL', 'TIMEZONE', 'REFERRAL', 'MODERATION'], - }, - ], - }, -]; +// Hierarchical settings tree — all 61 backend category keys mapped into 7 groups +export const SETTINGS_TREE: SettingsTreeConfig = { + specialItems: [ + { id: 'favorites', iconType: 'star' }, + { id: 'branding', icon: '🎨' }, + { id: 'theme', icon: '🌈' }, + { id: 'analytics', icon: '📊' }, + { id: 'buttons', icon: '📱' }, + ], + groups: [ + { + id: 'payments', + icon: '💳', + children: [ + { id: 'payments_general', categories: ['PAYMENT', 'PAYMENT_VERIFICATION'] }, + { id: 'payments_stars', categories: ['TELEGRAM'] }, + { id: 'payments_yookassa', categories: ['YOOKASSA'] }, + { id: 'payments_cryptobot', categories: ['CRYPTOBOT'] }, + { id: 'payments_cloudpayments', categories: ['CLOUDPAYMENTS'] }, + { id: 'payments_freekassa', categories: ['FREEKASSA'] }, + { id: 'payments_kassa_ai', categories: ['KASSA_AI'] }, + { id: 'payments_platega', categories: ['PLATEGA'] }, + { id: 'payments_pal24', categories: ['PAL24'] }, + { id: 'payments_heleket', categories: ['HELEKET'] }, + { id: 'payments_mulenpay', categories: ['MULENPAY'] }, + { id: 'payments_tribute', categories: ['TRIBUTE'] }, + { id: 'payments_wata', categories: ['WATA'] }, + { id: 'payments_riopay', categories: ['RIOPAY'] }, + { id: 'payments_severpay', categories: ['SEVERPAY'] }, + ], + }, + { + id: 'subscriptions', + icon: '📦', + children: [ + { id: 'subs_core', categories: ['SUBSCRIPTIONS_CORE'] }, + { id: 'subs_trial', categories: ['TRIAL'] }, + { id: 'subs_pricing', categories: ['SUBSCRIPTION_PRICES'] }, + { id: 'subs_periods', categories: ['PERIODS'] }, + { id: 'subs_traffic', categories: ['TRAFFIC', 'TRAFFIC_PACKAGES'] }, + { id: 'subs_simple', categories: ['SIMPLE_SUBSCRIPTION'] }, + { id: 'subs_autopay', categories: ['AUTOPAY'] }, + ], + }, + { + id: 'interface', + icon: '🖥️', + children: [ + { + id: 'iface_general', + categories: ['INTERFACE', 'INTERFACE_BRANDING', 'INTERFACE_SUBSCRIPTION'], + }, + { id: 'iface_connect', categories: ['CONNECT_BUTTON'] }, + { id: 'iface_miniapp', categories: ['MINIAPP'] }, + { id: 'iface_happ', categories: ['HAPP'] }, + { id: 'iface_widget', categories: ['TELEGRAM_WIDGET'] }, + { id: 'iface_oidc', categories: ['TELEGRAM_OIDC'] }, + { id: 'iface_skip', categories: ['SKIP'] }, + { id: 'iface_additional', categories: ['ADDITIONAL'] }, + ], + }, + { + id: 'users', + icon: '👥', + children: [ + { id: 'users_support', categories: ['SUPPORT'] }, + { id: 'users_referral', categories: ['REFERRAL'] }, + { id: 'users_channel', categories: ['CHANNEL'] }, + { id: 'users_localization', categories: ['LOCALIZATION', 'TIMEZONE'] }, + { id: 'users_moderation', categories: ['MODERATION', 'BAN_NOTIFICATIONS'] }, + ], + }, + { + id: 'notifications', + icon: '🔔', + children: [ + { id: 'notif_user', categories: ['NOTIFICATIONS', 'WEBHOOK_NOTIFICATIONS'] }, + { id: 'notif_admin', categories: ['ADMIN_NOTIFICATIONS'] }, + { id: 'notif_reports', categories: ['ADMIN_REPORTS'] }, + ], + }, + { + id: 'database', + icon: '🗄️', + children: [ + { id: 'db_general', categories: ['DATABASE'] }, + { id: 'db_postgres', categories: ['POSTGRES'] }, + { id: 'db_sqlite', categories: ['SQLITE'] }, + { id: 'db_redis', categories: ['REDIS'] }, + ], + }, + { + id: 'system', + icon: '⚙️', + children: [ + { id: 'sys_core', categories: ['CORE', 'DEBUG'] }, + { id: 'sys_remnawave', categories: ['REMNAWAVE'] }, + { id: 'sys_webapi', categories: ['WEB_API', 'EXTERNAL_ADMIN'] }, + { id: 'sys_webhook', categories: ['WEBHOOK'] }, + { id: 'sys_server', categories: ['SERVER_STATUS'] }, + { id: 'sys_monitoring', categories: ['MONITORING'] }, + { id: 'sys_maintenance', categories: ['MAINTENANCE'] }, + { id: 'sys_backup', categories: ['BACKUP'] }, + { id: 'sys_version', categories: ['VERSION'] }, + { id: 'sys_logging', categories: ['LOG'] }, + ], + }, + ], +}; + +// Helper: find which group and sub-item a backend category key belongs to +export function findTreeLocation( + categoryKey: string, +): { groupId: string; subItemId: string } | null { + for (const group of SETTINGS_TREE.groups) { + for (const child of group.children) { + if (child.categories.includes(categoryKey)) { + return { groupId: group.id, subItemId: child.id }; + } + } + } + return null; +} + +// Helper: get all backend category keys for a given sub-item id +export function getCategoriesForSubItem(subItemId: string): string[] { + for (const group of SETTINGS_TREE.groups) { + const child = group.children.find((c) => c.id === subItemId); + if (child) return child.categories; + } + return []; +} // Theme preset type export interface ThemePreset { diff --git a/src/components/admin/index.ts b/src/components/admin/index.ts index a6a7d72..90c30ad 100644 --- a/src/components/admin/index.ts +++ b/src/components/admin/index.ts @@ -5,6 +5,7 @@ export * from './icons'; export * from './Toggle'; export * from './SettingInput'; export * from './SettingRow'; +export * from './SettingsTableRow'; export * from './AnalyticsTab'; export * from './BrandingTab'; export * from './ButtonsTab'; @@ -13,6 +14,8 @@ export * from './FavoritesTab'; export * from './SettingsTab'; export * from './SettingsMobileTabs'; export * from './SettingsSearch'; +export * from './SettingsTreeSidebar'; +export * from './QuickToggles'; export * from './LocaleTabs'; export * from './LocalizedInput'; diff --git a/src/components/dashboard/SubscriptionCardActive.tsx b/src/components/dashboard/SubscriptionCardActive.tsx index a3e09ad..cf2adeb 100644 --- a/src/components/dashboard/SubscriptionCardActive.tsx +++ b/src/components/dashboard/SubscriptionCardActive.tsx @@ -214,7 +214,7 @@ export default function SubscriptionCardActive({ haptic.notification('error'); return; } - navigate('/connection'); + navigate(`/connection?sub=${subscription.id}`); }} className={`mb-2.5 flex w-full items-center gap-3.5 rounded-[14px] p-3.5 text-left transition-shadow duration-300${isAtDeviceLimit ? 'cursor-not-allowed opacity-50' : ''}`} data-onboarding="connect-devices" @@ -312,7 +312,7 @@ export default function SubscriptionCardActive({
{/* Tariff badge — clickable */} 0 ? `${trafficRefreshCooldown}s` : t('common.refresh')} {t('dashboard.viewSubscription')} → diff --git a/src/components/dashboard/SubscriptionCardExpired.tsx b/src/components/dashboard/SubscriptionCardExpired.tsx index 7bcfb4e..499ab92 100644 --- a/src/components/dashboard/SubscriptionCardExpired.tsx +++ b/src/components/dashboard/SubscriptionCardExpired.tsx @@ -57,15 +57,18 @@ export default function SubscriptionCardExpired({ try { if (isDisabledDaily) { // Resume daily subscription via toggle pause endpoint - await subscriptionApi.togglePause(); + await subscriptionApi.togglePause(subscription.id); } else if (isDaily && subscription.tariff_id) { // Expired daily tariff — purchase for 1 day await subscriptionApi.purchaseTariff(subscription.tariff_id, 1); } else { - await subscriptionApi.renewSubscription(30); + await subscriptionApi.renewSubscription(30, subscription.id); } haptic.success(); - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ + predicate: (query) => Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); } catch (err: unknown) { @@ -257,7 +260,7 @@ export default function SubscriptionCardExpired({
{isLimited ? ( { + setIsKeyboardOpen(false); + }, [location.pathname]); + // Keyboard detection for hiding bottom nav useEffect(() => { const handleFocusIn = (e: FocusEvent) => { @@ -254,7 +259,7 @@ export function AppShell({ children }: AppShellProps) { // Desktop navigation items const desktopNavItems = [ { path: '/', label: t('nav.dashboard'), icon: HomeIcon }, - { path: '/subscription', label: t('nav.subscription'), icon: SubscriptionIcon }, + { path: '/subscriptions', label: t('nav.subscription'), icon: SubscriptionIcon }, { path: '/balance', label: t('nav.balance'), icon: CreditCardIcon }, { path: '/support', label: t('nav.support'), icon: ChatIcon }, { path: '/info', label: t('nav.info'), icon: InfoIcon }, diff --git a/src/components/layout/AppShell/DesktopSidebar.tsx b/src/components/layout/AppShell/DesktopSidebar.tsx index 4af5611..1f38940 100644 --- a/src/components/layout/AppShell/DesktopSidebar.tsx +++ b/src/components/layout/AppShell/DesktopSidebar.tsx @@ -79,7 +79,7 @@ export function DesktopSidebar({ const navItems = [ { path: '/', label: t('nav.dashboard'), icon: HomeIcon }, - { path: '/subscription', label: t('nav.subscription'), icon: SubscriptionIcon }, + { path: '/subscriptions', label: t('nav.subscription'), icon: SubscriptionIcon }, { path: '/balance', label: t('nav.balance'), icon: WalletIcon }, ...(referralEnabled ? [{ path: '/referral', label: t('nav.referral'), icon: UsersIcon }] : []), { path: '/support', label: t('nav.support'), icon: ChatIcon }, diff --git a/src/components/layout/AppShell/MobileBottomNav.tsx b/src/components/layout/AppShell/MobileBottomNav.tsx index 72463b7..9d72221 100644 --- a/src/components/layout/AppShell/MobileBottomNav.tsx +++ b/src/components/layout/AppShell/MobileBottomNav.tsx @@ -29,7 +29,7 @@ export function MobileBottomNav({ // When wheel is enabled, it replaces Support in the bottom nav (Support is still accessible via hamburger menu) const coreItems = [ { path: '/', label: t('nav.dashboard'), icon: HomeIcon }, - { path: '/subscription', label: t('nav.subscription'), icon: SubscriptionIcon }, + { path: '/subscriptions', label: t('nav.subscription'), icon: SubscriptionIcon }, { path: '/balance', label: t('nav.balance'), icon: WalletIcon }, ...(referralEnabled ? [{ path: '/referral', label: t('nav.referral'), icon: UsersIcon }] : []), ...(wheelEnabled diff --git a/src/components/navigation/CommandPalette/CommandPalette.tsx b/src/components/navigation/CommandPalette/CommandPalette.tsx index e5add68..dca8699 100644 --- a/src/components/navigation/CommandPalette/CommandPalette.tsx +++ b/src/components/navigation/CommandPalette/CommandPalette.tsx @@ -78,7 +78,7 @@ export function CommandPalette({ // Navigation items const navigationItems = [ { label: t('nav.dashboard'), icon: HomeIcon, path: '/' }, - { label: t('nav.subscription'), icon: SubscriptionIcon, path: '/subscription' }, + { label: t('nav.subscription'), icon: SubscriptionIcon, path: '/subscriptions' }, { label: t('nav.balance'), icon: WalletIcon, path: '/balance' }, ...(referralEnabled ? [{ label: t('nav.referral'), icon: UsersIcon, path: '/referral' }] : []), { label: t('nav.support'), icon: ChatIcon, path: '/support' }, @@ -100,7 +100,7 @@ export function CommandPalette({ { label: t('subscription.get_config') || 'Get VPN config', icon: DownloadIcon, - action: () => navigate('/subscription'), + action: () => navigate('/subscriptions'), }, { label: isDark ? t('theme.light') || 'Light mode' : t('theme.dark') || 'Dark mode', diff --git a/src/components/news/NewsSection.tsx b/src/components/news/NewsSection.tsx index 0c99707..0ea43ae 100644 --- a/src/components/news/NewsSection.tsx +++ b/src/components/news/NewsSection.tsx @@ -369,7 +369,7 @@ export default function NewsSection() { gcTime: 10 * 60_000, }); - const items = data?.items ?? []; + const items = useMemo(() => data?.items ?? [], [data?.items]); const total = data?.total ?? 0; const categories = data?.categories ?? []; diff --git a/src/components/subscription/PurchaseCTAButton.tsx b/src/components/subscription/PurchaseCTAButton.tsx index 2a6739d..1fa7b0f 100644 --- a/src/components/subscription/PurchaseCTAButton.tsx +++ b/src/components/subscription/PurchaseCTAButton.tsx @@ -5,13 +5,22 @@ import type { Subscription } from '../../types'; interface PurchaseCTAButtonProps { subscription: Subscription | null; + /** In multi-tariff mode, link to /subscriptions/:id/renew instead of /subscription/purchase */ + isMultiTariff?: boolean; } -export default function PurchaseCTAButton({ subscription }: PurchaseCTAButtonProps) { +export default function PurchaseCTAButton({ + subscription, + isMultiTariff = false, +}: PurchaseCTAButtonProps) { const { t } = useTranslation(); const isExpired = !subscription || (!subscription.is_active && !subscription.is_trial); const isTrial = subscription?.is_trial; + const isDaily = subscription?.is_daily; + + // Daily tariffs renew automatically — no manual renewal button needed in multi-tariff + if (isMultiTariff && isDaily && !isExpired) return null; const accentColor = isExpired ? '#FF3B5C' : 'rgb(var(--color-accent-400))'; @@ -25,10 +34,21 @@ export default function PurchaseCTAButton({ subscription }: PurchaseCTAButtonPro ? t('subscription.cta.expiredHint') : isTrial ? t('subscription.cta.trialHint') - : t('subscription.cta.activeHint'); + : isMultiTariff + ? t('subscription.cta.renewHint', 'Продление подписки') + : t('subscription.cta.activeHint'); + + // Trial → purchase page (buy a real tariff, trial can't be renewed) + // Multi-tariff active → per-subscription renew page + // Otherwise → purchase page + const linkTo = isTrial + ? '/subscription/purchase' + : isMultiTariff && subscription?.id + ? `/subscriptions/${subscription.id}/renew` + : '/subscription/purchase'; return ( - + string; +}) { + const isActive = status === 'active' || status === 'trial'; + const isLimited = status === 'limited'; + const isExpired = status === 'expired' || status === 'disabled'; + + if (isTrial) { + return ( + + + + + {t('subscription.statusTrial', 'Тестовая')} + + ); + } + + const color = isActive + ? 'bg-emerald-400/15 text-emerald-400 border-emerald-400/20' + : isLimited + ? 'bg-amber-400/15 text-amber-400 border-amber-400/20' + : 'bg-red-400/15 text-red-400 border-red-400/20'; + + const label = isActive + ? t('subscription.statusActive', 'Активна') + : isLimited + ? t('subscription.statusLimited', 'Ограничена') + : isExpired + ? t('subscription.statusExpired', 'Истекла') + : status; + + return ( + + {label} + + ); +} + +export default function SubscriptionListCard({ + subscription, + onClick, +}: { + subscription: SubscriptionListItem; + onClick: () => void; +}) { + const { t, i18n } = useTranslation(); + const { isDark } = useTheme(); + const g = getGlassColors(isDark); + const { impact } = useHaptic(); + + const handleClick = () => { + impact('light'); + onClick(); + }; + + const isTrial = subscription.is_trial; + const isActive = subscription.status === 'active' || subscription.status === 'trial'; + const isExpired = subscription.status === 'expired' || subscription.status === 'disabled'; + const trafficLimit = subscription.traffic_limit_gb; + const trafficUsed = subscription.traffic_used_gb; + const isUnlimited = trafficLimit === 0; + const trafficPercent = isUnlimited + ? 0 + : trafficLimit > 0 + ? Math.min(100, (trafficUsed / trafficLimit) * 100) + : 0; + const trafficColor = + trafficPercent >= 90 ? 'bg-red-400' : trafficPercent >= 70 ? 'bg-amber-400' : 'bg-emerald-400'; + + const borderColor = isTrial + ? 'rgba(251,191,36,0.2)' + : isExpired + ? 'rgba(255,59,92,0.15)' + : g.cardBorder; + + const bgColor = isTrial + ? isDark + ? 'rgba(251,191,36,0.04)' + : 'rgba(251,191,36,0.03)' + : isExpired + ? isDark + ? 'rgba(255,59,92,0.04)' + : 'rgba(255,59,92,0.03)' + : g.cardBg; + + return ( + + ); +} diff --git a/src/i18n.ts b/src/i18n.ts index 2b20910..25ab022 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -46,6 +46,8 @@ i18n react: { useSuspense: false, }, + + showSupportNotice: false, }); // Load detected language + fallback on startup diff --git a/src/locales/en.json b/src/locales/en.json index c2241f4..ef4ea57 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -316,7 +316,8 @@ "trafficReset": { "DAY": "Resets daily", "WEEK": "Resets weekly", - "MONTH": "Resets monthly" + "MONTH": "Resets monthly", + "MONTH_ROLLING": "Resets every 30 days" }, "traffic": "Traffic", "unlimited": "Unlimited", @@ -1116,6 +1117,17 @@ "panel": { "title": "Admin Panel", "subtitle": "System management", + "statsUptime": "Uptime", + "statsBot": "Bot", + "statsCabinet": "Cabinet", + "statsTrials": "Trials", + "statsPaid": "Paid", + "statsOnline": "Online", + "statsToday": "today", + "searchPlaceholder": "Search...", + "searchEmpty": "Nothing found", + "searchEmptyHint": "Try a different query", + "searchClear": "Clear search", "dashboardDesc": "Statistics and system monitoring", "ticketsDesc": "Handle user support tickets", "settingsDesc": "System settings and parameters", @@ -1481,16 +1493,26 @@ "tabs": { "overview": "Overview", "nodes": "Nodes", + "traffic": "Traffic", "squads": "Squads", "sync": "Sync" }, + "traffic": { + "noData": "No traffic data available", + "totalDownload": "Download", + "totalUpload": "Upload", + "totalTraffic": "Total", + "online": "online", + "inbounds": "Inbounds", + "outbounds": "Outbounds" + }, "overview": { "system": "System", "usersOnline": "Online", "totalUsers": "Total Users", "nodesOnline": "Nodes Online", "connections": "Connections", - "bandwidth": "Realtime Bandwidth", + "bandwidth": "Inbound Traffic", "download": "Download", "upload": "Upload", "total": "Total", @@ -1814,8 +1836,11 @@ "title": "System Settings", "allCategories": "All categories", "noSettings": "No settings found", + "quickToggles": "Quick Toggles", "modified": "Modified", "readOnly": "Read only", + "badgeDb": "DB", + "badgeEnv": "ENV", "reset": "Reset", "categoriesCount": "categories", "settingsCount": "settings", @@ -1888,6 +1913,84 @@ "quickPresets": "Quick presets", "resetAllColors": "Reset all colors", "statusColors": "Status colors", + "favorites": "Favorites", + "branding": "Branding", + "theme": "Theme", + "payments": "Payments", + "subscriptions": "Subscriptions", + "interface": "Interface", + "notifications": "Notifications", + "database": "Database", + "system": "System", + "users": "Users", + "customization": "Customization", + "settingsLabel": "Settings", + "backToAdmin": "Back to admin", + "totalCount": "{{count}} total", + "modifiedCount": "{{count}} modified", + "groups": { + "payments": "Payments", + "subscriptions": "Subscriptions", + "interface": "Interface", + "users": "Users", + "notifications": "Notifications", + "database": "Database", + "system": "System" + }, + "tree": { + "payments_general": "General", + "payments_stars": "Telegram Stars", + "payments_yookassa": "YooKassa", + "payments_cryptobot": "CryptoBot", + "payments_cloudpayments": "CloudPayments", + "payments_freekassa": "FreeKassa", + "payments_kassa_ai": "Kassa AI", + "payments_platega": "Platega", + "payments_pal24": "PAL24", + "payments_heleket": "Heleket", + "payments_mulenpay": "MulenPay", + "payments_tribute": "Tribute", + "payments_wata": "Wata", + "payments_riopay": "RioPay", + "payments_severpay": "SeverPay", + "subs_core": "Core", + "subs_trial": "Trial", + "subs_pricing": "Pricing", + "subs_periods": "Periods", + "subs_traffic": "Traffic", + "subs_simple": "Simple subscription", + "subs_autopay": "Auto-pay", + "iface_general": "General", + "iface_connect": "Connect button", + "iface_miniapp": "MiniApp", + "iface_happ": "HAPP", + "iface_widget": "Login Widget", + "iface_oidc": "OIDC", + "iface_skip": "Skip steps", + "iface_additional": "Additional", + "users_support": "Support", + "users_referral": "Referral", + "users_channel": "Channel", + "users_localization": "Localization", + "users_moderation": "Moderation", + "notif_user": "User notifications", + "notif_admin": "Admin notifications", + "notif_reports": "Reports", + "db_general": "General", + "db_postgres": "PostgreSQL", + "db_sqlite": "SQLite", + "db_redis": "Redis", + "sys_core": "Core & Debug", + "sys_remnawave": "RemnaWave", + "sys_webapi": "Web API", + "sys_webhook": "Webhook", + "sys_server": "Server status", + "sys_monitoring": "Monitoring", + "sys_maintenance": "Maintenance", + "sys_backup": "Backup", + "sys_version": "Version", + "sys_logging": "Logging" + }, "categories": { "TELEGRAM_WIDGET": "Telegram Login Widget", "TELEGRAM_OIDC": "Telegram Login (OIDC)" @@ -2198,6 +2301,8 @@ "resetModeWeeklyDesc": "Reset every week", "resetModeMonthly": "Monthly", "resetModeMonthlyDesc": "Reset every month", + "resetModeMonthRolling": "Rolling month", + "resetModeMonthRollingDesc": "Reset every 30 days from first connection", "resetModeNever": "Never", "resetModeNeverDesc": "Traffic is not reset", "cancelButton": "Cancel", @@ -2835,7 +2940,8 @@ "balance": "Balance", "sync": "Synchronization", "tickets": "Tickets", - "gifts": "Gifts" + "gifts": "Gifts", + "referrals": "Referrals" }, "noTickets": "No tickets from this user", "ticketsCount": "tickets", @@ -2947,6 +3053,7 @@ "recentTransactions": "Recent transactions" }, "sync": { + "selectSubscription": "Select subscription", "hasDifferences": "Differences found", "synced": "Synchronized", "bot": "Bot", @@ -2994,6 +3101,28 @@ "unknownUser": "Unknown", "totalSent": "Total sent", "totalReceived": "Total received" + }, + "referrals": { + "referredBy": "Referred By", + "noReferrer": "No referrer assigned", + "assignReferrer": "Assign Referrer", + "removeReferrer": "Remove", + "referrerAssigned": "Referrer assigned successfully", + "referrerRemoved": "Referrer removed successfully", + "totalReferrals": "Total Referrals", + "totalEarnings": "Total Earnings", + "commission": "Commission", + "referralCode": "Referral Code", + "default": "default", + "referralsList": "Referrals", + "noReferrals": "No referrals yet", + "removeReferral": "Remove referral", + "referralRemoved": "Referral removed successfully", + "addReferral": "Add Referral", + "referralAdded": "Referral added successfully", + "searchPlaceholder": "Search by name, email, or Telegram ID...", + "alreadyReferred": "already has referrer", + "noUsersFound": "No users found" } } }, diff --git a/src/locales/fa.json b/src/locales/fa.json index 49a1740..3cf08b8 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -277,7 +277,8 @@ "trafficReset": { "DAY": "بازنشانی روزانه", "WEEK": "بازنشانی هفتگی", - "MONTH": "بازنشانی ماهانه" + "MONTH": "بازنشانی ماهانه", + "MONTH_ROLLING": "بازنشانی هر ۳۰ روز" }, "traffic": "ترافیک", "unlimited": "نامحدود", @@ -950,6 +951,17 @@ "panel": { "title": "پنل مدیریت", "subtitle": "مدیریت سیستم", + "statsUptime": "آپتایم", + "statsBot": "ربات", + "statsCabinet": "کابینت", + "statsTrials": "آزمایشی", + "statsPaid": "پولی", + "statsOnline": "آنلاین", + "statsToday": "امروز", + "searchPlaceholder": "جستجو...", + "searchEmpty": "چیزی پیدا نشد", + "searchEmptyHint": "عبارت دیگری امتحان کنید", + "searchClear": "پاک کردن جستجو", "dashboardDesc": "آمار و مانیتورینگ سیستم", "ticketsDesc": "مدیریت تیکت‌های کاربران", "settingsDesc": "تنظیمات و پارامترهای سیستم", @@ -1485,6 +1497,7 @@ "title": "تنظیمات سیستم", "allCategories": "همه دسته‌ها", "noSettings": "تنظیماتی یافت نشد", + "quickToggles": "تغییر سریع", "modified": "تغییر یافته", "readOnly": "فقط خواندنی", "reset": "بازنشانی", @@ -1559,6 +1572,86 @@ "quickPresets": "پیش‌تنظیم‌های سریع", "resetAllColors": "بازنشانی همه رنگ‌ها", "statusColors": "رنگ‌های وضعیت", + "favorites": "علاقه‌مندی‌ها", + "branding": "برندسازی", + "theme": "پوسته", + "payments": "پرداخت‌ها", + "subscriptions": "اشتراک‌ها", + "interface": "رابط کاربری", + "notifications": "اعلان‌ها", + "database": "پایگاه داده", + "system": "سیستم", + "users": "کاربران", + "customization": "سفارشی‌سازی", + "settingsLabel": "تنظیمات", + "backToAdmin": "بازگشت به مدیریت", + "totalCount": "{{count}} مورد", + "modifiedCount": "{{count}} تغییر یافته", + "badgeDb": "پایگاه داده", + "badgeEnv": "متغیر محیطی", + "groups": { + "payments": "پرداخت‌ها", + "subscriptions": "اشتراک‌ها", + "interface": "رابط کاربری", + "users": "کاربران", + "notifications": "اعلان‌ها", + "database": "پایگاه داده", + "system": "سیستم" + }, + "tree": { + "payments_general": "عمومی", + "payments_stars": "Telegram Stars", + "payments_yookassa": "YooKassa", + "payments_cryptobot": "CryptoBot", + "payments_cloudpayments": "CloudPayments", + "payments_freekassa": "FreeKassa", + "payments_kassa_ai": "Kassa AI", + "payments_platega": "Platega", + "payments_pal24": "PAL24", + "payments_heleket": "Heleket", + "payments_mulenpay": "MulenPay", + "payments_tribute": "Tribute", + "payments_wata": "Wata", + "payments_riopay": "RioPay", + "payments_severpay": "SeverPay", + "subs_core": "هسته", + "subs_trial": "آزمایشی", + "subs_pricing": "قیمت‌گذاری", + "subs_periods": "دوره‌ها", + "subs_traffic": "ترافیک", + "subs_simple": "اشتراک ساده", + "subs_autopay": "پرداخت خودکار", + "iface_general": "عمومی", + "iface_connect": "دکمه اتصال", + "iface_miniapp": "MiniApp", + "iface_happ": "HAPP", + "iface_widget": "ویجت ورود", + "iface_oidc": "OIDC", + "iface_skip": "رد کردن مراحل", + "iface_additional": "اضافی", + "users_support": "پشتیبانی", + "users_referral": "معرفی", + "users_channel": "کانال", + "users_localization": "بومی‌سازی", + "users_moderation": "مدیریت محتوا", + "notif_user": "اعلان‌های کاربر", + "notif_admin": "اعلان‌های مدیر", + "notif_reports": "گزارش‌ها", + "db_general": "عمومی", + "db_postgres": "PostgreSQL", + "db_sqlite": "SQLite", + "db_redis": "Redis", + "sys_core": "هسته و اشکال‌زدایی", + "sys_remnawave": "RemnaWave", + "sys_webapi": "Web API", + "sys_webhook": "Webhook", + "sys_server": "وضعیت سرور", + "sys_monitoring": "نظارت", + "sys_maintenance": "نگهداری", + "sys_backup": "پشتیبان‌گیری", + "sys_version": "نسخه", + "sys_logging": "لاگ‌ها" + }, "categories": { "TELEGRAM_WIDGET": "Telegram Login Widget", "TELEGRAM_OIDC": "Telegram Login (OIDC)" @@ -1853,6 +1946,8 @@ "resetModeWeeklyDesc": "بازنشانی هر هفته", "resetModeMonthly": "ماهانه", "resetModeMonthlyDesc": "بازنشانی هر ماه", + "resetModeMonthRolling": "ماه متحرک", + "resetModeMonthRollingDesc": "بازنشانی هر ۳۰ روز از اولین اتصال", "resetModeNever": "هرگز", "resetModeNeverDesc": "ترافیک بازنشانی نمی‌شود", "cancelButton": "لغو", @@ -2467,7 +2562,8 @@ "balance": "موجودی", "sync": "همگام‌سازی", "tickets": "تیکت‌ها", - "gifts": "هدایا" + "gifts": "هدایا", + "referrals": "معرفی‌ها" }, "noTickets": "این کاربر تیکتی ندارد", "ticketsCount": "تیکت", @@ -2575,6 +2671,7 @@ "recentTransactions": "تراکنش‌های اخیر" }, "sync": { + "selectSubscription": "انتخاب اشتراک", "hasDifferences": "اختلاف وجود دارد", "synced": "همگام‌سازی شده", "bot": "ربات", @@ -2622,6 +2719,28 @@ "unknownUser": "ناشناس", "totalSent": "کل ارسال شده", "totalReceived": "کل دریافت شده" + }, + "referrals": { + "referredBy": "معرفی‌شده توسط", + "noReferrer": "معرفی‌کننده‌ای تعیین نشده", + "assignReferrer": "تعیین معرفی‌کننده", + "removeReferrer": "حذف", + "referrerAssigned": "معرفی‌کننده تعیین شد", + "referrerRemoved": "معرفی‌کننده حذف شد", + "totalReferrals": "کل معرفی‌ها", + "totalEarnings": "کل درآمد", + "commission": "کمیسیون", + "referralCode": "کد معرفی", + "default": "پیش‌فرض", + "referralsList": "لیست معرفی‌ها", + "noReferrals": "هنوز معرفی‌ای نیست", + "removeReferral": "حذف معرفی", + "referralRemoved": "معرفی حذف شد", + "addReferral": "افزودن معرفی", + "referralAdded": "معرفی اضافه شد", + "searchPlaceholder": "جستجو بر اساس نام، ایمیل یا شناسه تلگرام...", + "alreadyReferred": "قبلاً معرفی شده", + "noUsersFound": "کاربری یافت نشد" } } }, @@ -2740,9 +2859,19 @@ "tabs": { "overview": "نمای کلی", "nodes": "گره‌ها", + "traffic": "ترافیک", "squads": "گروه‌ها", "sync": "همگام‌سازی" }, + "traffic": { + "noData": "داده ترافیکی موجود نیست", + "totalDownload": "دانلود", + "totalUpload": "آپلود", + "totalTraffic": "مجموع", + "online": "آنلاین", + "inbounds": "ورودی‌ها", + "outbounds": "خروجی‌ها" + }, "nodes": { "confirmRestartAll": "آیا مطمئنید می‌خواهید همه گره‌ها را راه‌اندازی مجدد کنید؟", "disable": "غیرفعال", diff --git a/src/locales/ru.json b/src/locales/ru.json index a2c095d..4fa428e 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -331,7 +331,8 @@ "trafficReset": { "DAY": "Сброс ежедневно", "WEEK": "Сброс еженедельно", - "MONTH": "Сброс ежемесячно" + "MONTH": "Сброс ежемесячно", + "MONTH_ROLLING": "Сброс раз в 30 дней" }, "traffic": "Трафик", "unlimited": "Безлимит", @@ -1137,6 +1138,17 @@ "panel": { "title": "Панель администратора", "subtitle": "Управление системой", + "statsUptime": "Аптайм", + "statsBot": "Бот", + "statsCabinet": "Кабинет", + "statsTrials": "Триалы", + "statsPaid": "Платные", + "statsOnline": "Онлайн", + "statsToday": "сегодня", + "searchPlaceholder": "Поиск...", + "searchEmpty": "Ничего не найдено", + "searchEmptyHint": "Попробуйте другой запрос", + "searchClear": "Очистить поиск", "dashboardDesc": "Статистика и мониторинг системы", "ticketsDesc": "Обработка обращений пользователей", "settingsDesc": "Настройки системы и параметры", @@ -1502,16 +1514,26 @@ "tabs": { "overview": "Обзор", "nodes": "Ноды", + "traffic": "Трафик", "squads": "Сквады", "sync": "Синхронизация" }, + "traffic": { + "noData": "Нет данных о трафике", + "totalDownload": "Загрузка", + "totalUpload": "Отдача", + "totalTraffic": "Всего", + "online": "онлайн", + "inbounds": "Inbounds", + "outbounds": "Outbounds" + }, "overview": { "system": "Система", "usersOnline": "Онлайн", "totalUsers": "Всего пользователей", "nodesOnline": "Нод онлайн", "connections": "Подключений", - "bandwidth": "Пропускная способность", + "bandwidth": "Трафик по inbounds", "download": "Загрузка", "upload": "Отдача", "total": "Всего", @@ -1839,8 +1861,11 @@ "title": "Настройки системы", "allCategories": "Все категории", "noSettings": "Настройки не найдены", + "quickToggles": "Быстрые переключатели", "modified": "Изменено", "readOnly": "Только чтение", + "badgeDb": "БД", + "badgeEnv": "ENV", "reset": "Сбросить", "categoriesCount": "категорий", "settingsCount": "настроек", @@ -1914,6 +1939,74 @@ "googleLabelPlaceholder": "Например: AbCdEfGhIjKl", "googleLabelHint": "Метка конверсии для отслеживания событий", "analyticsHint": "Счётчики автоматически встраиваются на все страницы кабинета. После сохранения ID скрипты подключаются при следующей загрузке страницы. Для удаления счётчика очистите поле и сохраните.", + "customization": "Кастомизация", + "settingsLabel": "Настройки", + "backToAdmin": "Назад к админке", + "totalCount": "{{count}} всего", + "modifiedCount": "{{count}} изменено", + "groups": { + "payments": "Платежи", + "subscriptions": "Подписки", + "interface": "Интерфейс", + "users": "Пользователи", + "notifications": "Уведомления", + "database": "База данных", + "system": "Система" + }, + "tree": { + "payments_general": "Основные", + "payments_stars": "Telegram Stars", + "payments_yookassa": "YooKassa", + "payments_cryptobot": "CryptoBot", + "payments_cloudpayments": "CloudPayments", + "payments_freekassa": "FreeKassa", + "payments_kassa_ai": "Kassa AI", + "payments_platega": "Platega", + "payments_pal24": "PAL24", + "payments_heleket": "Heleket", + "payments_mulenpay": "MulenPay", + "payments_tribute": "Tribute", + "payments_wata": "Wata", + "payments_riopay": "RioPay", + "payments_severpay": "SeverPay", + "subs_core": "Основные", + "subs_trial": "Пробный период", + "subs_pricing": "Цены", + "subs_periods": "Периоды", + "subs_traffic": "Трафик", + "subs_simple": "Простая подписка", + "subs_autopay": "Автоплатёж", + "iface_general": "Основные", + "iface_connect": "Кнопка подключения", + "iface_miniapp": "MiniApp", + "iface_happ": "HAPP", + "iface_widget": "Виджет входа", + "iface_oidc": "OIDC", + "iface_skip": "Пропуск шагов", + "iface_additional": "Дополнительно", + "users_support": "Поддержка", + "users_referral": "Реферальная система", + "users_channel": "Канал", + "users_localization": "Локализация", + "users_moderation": "Модерация", + "notif_user": "Уведомления пользователей", + "notif_admin": "Уведомления админов", + "notif_reports": "Отчёты", + "db_general": "Основные", + "db_postgres": "PostgreSQL", + "db_sqlite": "SQLite", + "db_redis": "Redis", + "sys_core": "Ядро и отладка", + "sys_remnawave": "RemnaWave", + "sys_webapi": "Web API", + "sys_webhook": "Webhook", + "sys_server": "Статус сервера", + "sys_monitoring": "Мониторинг", + "sys_maintenance": "Обслуживание", + "sys_backup": "Бэкап", + "sys_version": "Версия", + "sys_logging": "Логи" + }, "presets": { "standard": "Стандарт", "ocean": "Океан", @@ -2162,6 +2255,8 @@ "Devices Selection Enabled": "Выбор устройств", "Max Devices Limit": "Макс. устройств", "Price Per Device": "Цена за устройство", + "Multi Tariff Enabled": "Мультитарифный режим", + "Max Active Subscriptions": "Макс. подписок", "Sales Mode": "Режим продаж", "Available Renewal Periods": "Периоды продления", "Available Subscription Periods": "Периоды подписки", @@ -2712,6 +2807,8 @@ "resetModeWeeklyDesc": "Сброс каждую неделю", "resetModeMonthly": "Ежемесячно", "resetModeMonthlyDesc": "Сброс каждый месяц", + "resetModeMonthRolling": "Скользящий месяц", + "resetModeMonthRollingDesc": "Сброс через 30 дней от первого подключения", "resetModeNever": "Никогда", "resetModeNeverDesc": "Трафик не сбрасывается", "cancelButton": "Отмена", @@ -3357,7 +3454,8 @@ "balance": "Баланс", "sync": "Синхронизация", "tickets": "Тикеты", - "gifts": "Подарки" + "gifts": "Подарки", + "referrals": "Рефералы" }, "noTickets": "У пользователя нет тикетов", "ticketsCount": "тикетов", @@ -3468,6 +3566,7 @@ "recentTransactions": "Последние транзакции" }, "sync": { + "selectSubscription": "Выберите подписку", "hasDifferences": "Есть расхождения", "synced": "Синхронизировано", "bot": "Бот", @@ -3515,6 +3614,28 @@ "unknownUser": "Неизвестный", "totalSent": "Всего отправлено", "totalReceived": "Всего получено" + }, + "referrals": { + "referredBy": "Приглашён", + "noReferrer": "Реферер не назначен", + "assignReferrer": "Назначить реферера", + "removeReferrer": "Удалить", + "referrerAssigned": "Реферер назначен", + "referrerRemoved": "Реферер удалён", + "totalReferrals": "Всего рефералов", + "totalEarnings": "Всего заработано", + "commission": "Комиссия", + "referralCode": "Реферальный код", + "default": "стандарт", + "referralsList": "Рефералы", + "noReferrals": "Рефералов пока нет", + "removeReferral": "Удалить реферала", + "referralRemoved": "Реферал удалён", + "addReferral": "Добавить реферала", + "referralAdded": "Реферал добавлен", + "searchPlaceholder": "Поиск по имени, email или Telegram ID...", + "alreadyReferred": "уже чей-то реферал", + "noUsersFound": "Пользователи не найдены" } } }, diff --git a/src/locales/zh.json b/src/locales/zh.json index 9dc56c7..aa9e1d8 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -277,7 +277,8 @@ "trafficReset": { "DAY": "每日重置", "WEEK": "每周重置", - "MONTH": "每月重置" + "MONTH": "每月重置", + "MONTH_ROLLING": "每30天重置" }, "traffic": "流量", "unlimited": "无限", @@ -950,6 +951,17 @@ "panel": { "title": "管理面板", "subtitle": "系统管理", + "statsUptime": "运行时间", + "statsBot": "机器人", + "statsCabinet": "控制台", + "statsTrials": "试用", + "statsPaid": "付费", + "statsOnline": "在线", + "statsToday": "今日", + "searchPlaceholder": "搜索...", + "searchEmpty": "未找到结果", + "searchEmptyHint": "请尝试其他关键词", + "searchClear": "清除搜索", "dashboardDesc": "统计和系统监控", "ticketsDesc": "处理用户工单", "settingsDesc": "系统设置和参数", @@ -1523,6 +1535,7 @@ "title": "系统设置", "allCategories": "所有分类", "noSettings": "未找到设置", + "quickToggles": "快速开关", "modified": "已修改", "readOnly": "只读", "reset": "重置", @@ -1597,6 +1610,86 @@ "quickPresets": "快速预设", "resetAllColors": "重置所有颜色", "statusColors": "状态颜色", + "favorites": "收藏", + "branding": "品牌", + "theme": "主题", + "payments": "支付", + "subscriptions": "订阅", + "interface": "界面", + "notifications": "通知", + "database": "数据库", + "system": "系统", + "users": "用户", + "customization": "自定义", + "settingsLabel": "设置", + "backToAdmin": "返回管理面板", + "totalCount": "共 {{count}} 项", + "modifiedCount": "已修改 {{count}} 项", + "badgeDb": "数据库", + "badgeEnv": "环境变量", + "groups": { + "payments": "支付", + "subscriptions": "订阅", + "interface": "界面", + "users": "用户", + "notifications": "通知", + "database": "数据库", + "system": "系统" + }, + "tree": { + "payments_general": "通用", + "payments_stars": "Telegram Stars", + "payments_yookassa": "YooKassa", + "payments_cryptobot": "CryptoBot", + "payments_cloudpayments": "CloudPayments", + "payments_freekassa": "FreeKassa", + "payments_kassa_ai": "Kassa AI", + "payments_platega": "Platega", + "payments_pal24": "PAL24", + "payments_heleket": "Heleket", + "payments_mulenpay": "MulenPay", + "payments_tribute": "Tribute", + "payments_wata": "Wata", + "payments_riopay": "RioPay", + "payments_severpay": "SeverPay", + "subs_core": "核心", + "subs_trial": "试用", + "subs_pricing": "定价", + "subs_periods": "周期", + "subs_traffic": "流量", + "subs_simple": "简单订阅", + "subs_autopay": "自动支付", + "iface_general": "通用", + "iface_connect": "连接按钮", + "iface_miniapp": "MiniApp", + "iface_happ": "HAPP", + "iface_widget": "登录小部件", + "iface_oidc": "OIDC", + "iface_skip": "跳过步骤", + "iface_additional": "附加", + "users_support": "支持", + "users_referral": "推荐", + "users_channel": "频道", + "users_localization": "本地化", + "users_moderation": "审核", + "notif_user": "用户通知", + "notif_admin": "管理员通知", + "notif_reports": "报告", + "db_general": "通用", + "db_postgres": "PostgreSQL", + "db_sqlite": "SQLite", + "db_redis": "Redis", + "sys_core": "核心与调试", + "sys_remnawave": "RemnaWave", + "sys_webapi": "Web API", + "sys_webhook": "Webhook", + "sys_server": "服务器状态", + "sys_monitoring": "监控", + "sys_maintenance": "维护", + "sys_backup": "备份", + "sys_version": "版本", + "sys_logging": "日志" + }, "categories": { "TELEGRAM_WIDGET": "Telegram Login Widget", "TELEGRAM_OIDC": "Telegram Login (OIDC)" @@ -1890,6 +1983,8 @@ "resetModeWeeklyDesc": "每周重置", "resetModeMonthly": "每月", "resetModeMonthlyDesc": "每月重置", + "resetModeMonthRolling": "滚动月", + "resetModeMonthRollingDesc": "从首次连接起每30天重置", "resetModeNever": "从不", "resetModeNeverDesc": "流量不重置", "cancelButton": "取消", @@ -2466,7 +2561,8 @@ "balance": "余额", "sync": "同步", "tickets": "工单", - "gifts": "礼物" + "gifts": "礼物", + "referrals": "推荐" }, "noTickets": "该用户没有工单", "ticketsCount": "个工单", @@ -2574,6 +2670,7 @@ "recentTransactions": "最近交易" }, "sync": { + "selectSubscription": "选择订阅", "hasDifferences": "存在差异", "synced": "已同步", "bot": "机器人", @@ -2621,6 +2718,28 @@ "unknownUser": "未知", "totalSent": "已发送总数", "totalReceived": "已收到总数" + }, + "referrals": { + "referredBy": "推荐人", + "noReferrer": "未分配推荐人", + "assignReferrer": "分配推荐人", + "removeReferrer": "移除", + "referrerAssigned": "推荐人已分配", + "referrerRemoved": "推荐人已移除", + "totalReferrals": "总推荐数", + "totalEarnings": "总收益", + "commission": "佣金", + "referralCode": "推荐码", + "default": "默认", + "referralsList": "推荐列表", + "noReferrals": "暂无推荐", + "removeReferral": "移除推荐", + "referralRemoved": "推荐已移除", + "addReferral": "添加推荐", + "referralAdded": "推荐已添加", + "searchPlaceholder": "按名称、邮箱或 Telegram ID 搜索...", + "alreadyReferred": "已有推荐人", + "noUsersFound": "未找到用户" } } }, @@ -2748,9 +2867,19 @@ "tabs": { "overview": "概览", "nodes": "节点", + "traffic": "流量", "squads": "小队", "sync": "同步" }, + "traffic": { + "noData": "暂无流量数据", + "totalDownload": "下载", + "totalUpload": "上传", + "totalTraffic": "总计", + "online": "在线", + "inbounds": "入站", + "outbounds": "出站" + }, "nodes": { "confirmRestartAll": "确定要重启所有节点吗?", "disable": "禁用", diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx index a846752..685c7f8 100644 --- a/src/pages/AdminDashboard.tsx +++ b/src/pages/AdminDashboard.tsx @@ -10,6 +10,7 @@ import { type TopCampaignsResponse, type RecentPaymentsResponse, } from '../api/admin'; +import { formatUptime } from '../utils/format'; const CABINET_VERSION = __APP_VERSION__; import { useCurrency } from '../hooks/useCurrency'; @@ -254,14 +255,16 @@ function NodeCard({ node, onRestart, onToggle, isLoading }: NodeCardProps) {
{/* Xray Version & Uptime */} - {(node.xray_version || node.xray_uptime) && ( + {(node.versions?.xray || node.xray_uptime > 0) && (
- {node.xray_version && ( + {node.versions?.xray && ( - Xray {node.xray_version} + Xray {node.versions.xray} )} - {node.xray_uptime && Uptime: {node.xray_uptime}} + {node.xray_uptime > 0 && ( + Uptime: {formatUptime(node.xray_uptime)} + )}
)} diff --git a/src/pages/AdminNewsCreate.tsx b/src/pages/AdminNewsCreate.tsx index b132cab..088483e 100644 --- a/src/pages/AdminNewsCreate.tsx +++ b/src/pages/AdminNewsCreate.tsx @@ -165,10 +165,49 @@ function isSafeUrl(url: string | null | undefined): boolean { } // --- Slug utility --- +const TRANSLIT_MAP: Record = { + а: 'a', + б: 'b', + в: 'v', + г: 'g', + д: 'd', + е: 'e', + ё: 'yo', + ж: 'zh', + з: 'z', + и: 'i', + й: 'y', + к: 'k', + л: 'l', + м: 'm', + н: 'n', + о: 'o', + п: 'p', + р: 'r', + с: 's', + т: 't', + у: 'u', + ф: 'f', + х: 'kh', + ц: 'ts', + ч: 'ch', + ш: 'sh', + щ: 'shch', + ъ: '', + ы: 'y', + ь: '', + э: 'e', + ю: 'yu', + я: 'ya', +}; + function generateSlug(title: string): string { - return title - .toLowerCase() - .replace(/[^\w\s\-а-яёА-ЯЁ]/g, '') + const lower = title.toLowerCase(); + const transliterated = Array.from(lower) + .map((ch) => TRANSLIT_MAP[ch] ?? ch) + .join(''); + return transliterated + .replace(/[^a-z0-9\s-]/g, '') .replace(/[\s_]+/g, '-') .replace(/-+/g, '-') .replace(/^-+|-+$/g, '') diff --git a/src/pages/AdminPanel.tsx b/src/pages/AdminPanel.tsx index 83753c4..f8b8f81 100644 --- a/src/pages/AdminPanel.tsx +++ b/src/pages/AdminPanel.tsx @@ -1,725 +1,1059 @@ +import { useState, useEffect, useRef, useCallback, useMemo, memo } from 'react'; import { Link } from 'react-router'; import { useTranslation } from 'react-i18next'; -import { RemnawaveIcon, ArrowPathIcon } from '../components/icons'; import { usePermissionStore } from '@/store/permissions'; +import { statsApi, type SystemInfo, type DashboardStats } from '@/api/admin'; +import { useAnimatedNumber } from '@/hooks/useAnimatedNumber'; +import { useTelegramSDK } from '@/hooks/useTelegramSDK'; +import { cn } from '@/lib/utils'; -// Group header icons -const AnalyticsGroupIcon = () => ( - - +const CABINET_VERSION = __APP_VERSION__; +const IS_MAC = /Mac|iPhone|iPod|iPad/i.test(navigator.userAgent); + +// ─── Inline SVG Icons (lightweight, no external deps) ─── + +const SvgIcon = ({ + children, + className, + ...props +}: React.SVGProps & { children: React.ReactNode }) => ( + ); -const UsersGroupIcon = () => ( - - +// Stats bar icons (16x16 viewBox) +const StatUptimeIcon = () => ( + +); +const StatBotIcon = () => ( + +); +const StatCabinetIcon = () => ( + +); +const StatTrialIcon = () => ( + +); +const StatPaidIcon = () => ( + ); -const TariffsGroupIcon = () => ( - - - -); +// Section nav icons (24x24 viewBox) +const icons = { + 'bar-chart': ( + + + + + + + + ), + 'credit-card': ( + + + + + ), + activity: ( + + + + ), + trending: ( + + + + + ), + users: ( + + + + + + + ), + ticket: ( + + + + + ), + 'shield-alert': ( + + + + + + ), + tag: ( + + + + + ), + gift: ( + + + + + + ), + percent: ( + + + + + + ), + sparkle: ( + + + + ), + wallet: ( + + + + + + ), + layout: ( + + + + + + ), + newspaper: ( + + + + + + ), + megaphone: ( + + + + + ), + send: ( + + + + + ), + pin: ( + + + + + ), + 'circle-dot': ( + + + + + ), + handshake: ( + + + + + + + ), + 'arrow-up': ( + + + + + + ), + network: ( + + + + + + + + ), + radio: ( + + + + + + + + ), + settings: ( + + + + + ), + app: ( + + + + + ), + server: ( + + + + + + + ), + remnawave: ( + + ), + mail: ( + + + + + ), + refresh: ( + + + + + + + ), + shield: ( + + + + ), + 'user-check': ( + + + + + + ), + lock: ( + + + + + ), + scroll: ( + + + + + + ), + search: ( + + + + + ), + chevron: ( + + + + ), + x: ( + + + + + ), +} as const; -const MarketingGroupIcon = () => ( - - - -); +type IconName = keyof typeof icons; -const SystemGroupIcon = () => ( - - - - -); +// ─── Section Data ─── -// Modern icons with consistent styling -const ChartBarIcon = () => ( - - - -); - -const BanknotesIcon = () => ( - - - -); - -const UsersIcon = () => ( - - - -); - -const ChatBubbleIcon = () => ( - - - -); - -const NoSymbolIcon = () => ( - - - -); - -const CreditCardIcon = () => ( - - - -); - -const TicketIcon = () => ( - - - -); - -const GiftIcon = () => ( - - - -); - -const UserGroupIcon = () => ( - - - -); - -const MegaphoneIcon = () => ( - - - -); - -const PinnedMessageIcon = () => ( - - - - -); - -const PaperAirplaneIcon = () => ( - - - -); - -const SparklesIcon = () => ( - - - -); - -const HandshakeIcon = () => ( - - - -); - -const CogIcon = () => ( - - - - -); - -const DevicePhoneMobileIcon = () => ( - - - -); - -const ServerStackIcon = () => ( - - - -); - -const EnvelopeIcon = () => ( - - - -); - -const ArrowsUpDownIcon = () => ( - - - -); - -const NetworkGraphIcon = () => ( - - - - -); - -const NewspaperIcon = () => ( - - - -); - -const ChevronRightIcon = () => ( - - - -); - -const SecurityGroupIcon = () => ( - - - -); - -const ShieldIcon = () => ( - - - -); - -const UserPlusIcon = () => ( - - - -); - -const DocumentCheckIcon = () => ( - - - -); - -const ClipboardDocumentListIcon = () => ( - - - -); - -const RectangleGroupIcon = () => ( - - - -); - -interface AdminItem { +interface AdminNavItem { + name: string; + icon: IconName; to: string; - icon: React.ReactNode; - title: string; - description: string; permission: string; } -interface AdminGroup { +interface AdminSection { id: string; - title: string; - icon: React.ReactNode; + titleKey: string; + accent: string; gradient: string; - borderColor: string; - iconBg: string; - iconColor: string; - items: AdminItem[]; + items: AdminNavItem[]; } -function AdminCard({ - to, - icon, - title, - description, - iconBg, - iconColor, -}: AdminItem & { iconBg: string; iconColor: string }) { +const sections: AdminSection[] = [ + { + id: 'analytics', + titleKey: 'admin.groups.analytics', + accent: 'rgb(var(--color-success-400))', + gradient: + 'linear-gradient(135deg, rgb(var(--color-success-400)), rgb(var(--color-accent-500)))', + items: [ + { + name: 'admin.nav.dashboard', + icon: 'bar-chart', + to: '/admin/dashboard', + permission: 'stats:read', + }, + { + name: 'admin.nav.payments', + icon: 'credit-card', + to: '/admin/payments', + permission: 'payments:read', + }, + { + name: 'admin.nav.trafficUsage', + icon: 'activity', + to: '/admin/traffic-usage', + permission: 'traffic:read', + }, + { + name: 'admin.nav.salesStats', + icon: 'trending', + to: '/admin/sales-stats', + permission: 'sales_stats:read', + }, + ], + }, + { + id: 'users', + titleKey: 'admin.groups.users', + accent: 'rgb(var(--color-accent-400))', + gradient: 'linear-gradient(135deg, rgb(var(--color-accent-400)), rgb(var(--color-error-400)))', + items: [ + { name: 'admin.nav.users', icon: 'users', to: '/admin/users', permission: 'users:read' }, + { + name: 'admin.nav.tickets', + icon: 'ticket', + to: '/admin/tickets', + permission: 'tickets:read', + }, + { + name: 'admin.nav.banSystem', + icon: 'shield-alert', + to: '/admin/ban-system', + permission: 'ban_system:read', + }, + ], + }, + { + id: 'tariffs', + titleKey: 'admin.groups.tariffs', + accent: 'rgb(var(--color-warning-400))', + gradient: 'linear-gradient(135deg, rgb(var(--color-warning-400)), rgb(var(--color-error-300)))', + items: [ + { name: 'admin.nav.tariffs', icon: 'tag', to: '/admin/tariffs', permission: 'tariffs:read' }, + { + name: 'admin.nav.promocodes', + icon: 'gift', + to: '/admin/promocodes', + permission: 'promocodes:read', + }, + { + name: 'admin.nav.promoGroups', + icon: 'percent', + to: '/admin/promo-groups', + permission: 'promo_groups:read', + }, + { + name: 'admin.nav.promoOffers', + icon: 'sparkle', + to: '/admin/promo-offers', + permission: 'promo_offers:read', + }, + { + name: 'admin.nav.paymentMethods', + icon: 'wallet', + to: '/admin/payment-methods', + permission: 'payment_methods:read', + }, + { + name: 'admin.nav.landings', + icon: 'layout', + to: '/admin/landings', + permission: 'landings:read', + }, + ], + }, + { + id: 'marketing', + titleKey: 'admin.groups.marketing', + accent: 'rgb(var(--color-accent-300))', + gradient: 'linear-gradient(135deg, rgb(var(--color-accent-300)), rgb(var(--color-accent-600)))', + items: [ + { name: 'admin.nav.news', icon: 'newspaper', to: '/admin/news', permission: 'news:read' }, + { + name: 'admin.nav.campaigns', + icon: 'megaphone', + to: '/admin/campaigns', + permission: 'campaigns:read', + }, + { + name: 'admin.nav.broadcasts', + icon: 'send', + to: '/admin/broadcasts', + permission: 'broadcasts:read', + }, + { + name: 'admin.nav.pinnedMessages', + icon: 'pin', + to: '/admin/pinned-messages', + permission: 'pinned_messages:read', + }, + { name: 'admin.nav.wheel', icon: 'circle-dot', to: '/admin/wheel', permission: 'wheel:read' }, + { + name: 'admin.nav.partners', + icon: 'handshake', + to: '/admin/partners', + permission: 'partners:read', + }, + { + name: 'admin.nav.withdrawals', + icon: 'arrow-up', + to: '/admin/withdrawals', + permission: 'withdrawals:read', + }, + { + name: 'admin.nav.referralNetwork', + icon: 'network', + to: '/admin/referral-network', + permission: 'stats:read', + }, + ], + }, + { + id: 'system', + titleKey: 'admin.groups.system', + accent: 'rgb(var(--color-accent-500))', + gradient: + 'linear-gradient(135deg, rgb(var(--color-accent-500)), rgb(var(--color-success-500)))', + items: [ + { + name: 'admin.nav.channelSubscriptions', + icon: 'radio', + to: '/admin/channel-subscriptions', + permission: 'channels:read', + }, + { + name: 'admin.nav.settings', + icon: 'settings', + to: '/admin/settings', + permission: 'settings:read', + }, + { name: 'admin.nav.apps', icon: 'app', to: '/admin/apps', permission: 'apps:read' }, + { + name: 'admin.nav.servers', + icon: 'server', + to: '/admin/servers', + permission: 'servers:read', + }, + { + name: 'admin.nav.remnawave', + icon: 'remnawave', + to: '/admin/remnawave', + permission: 'remnawave:read', + }, + { + name: 'admin.nav.emailTemplates', + icon: 'mail', + to: '/admin/email-templates', + permission: 'email_templates:read', + }, + { + name: 'admin.nav.updates', + icon: 'refresh', + to: '/admin/updates', + permission: 'updates:read', + }, + ], + }, + { + id: 'security', + titleKey: 'admin.groups.security', + accent: 'rgb(var(--color-error-400))', + gradient: 'linear-gradient(135deg, rgb(var(--color-error-400)), rgb(var(--color-accent-600)))', + items: [ + { name: 'admin.nav.roles', icon: 'shield', to: '/admin/roles', permission: 'roles:read' }, + { + name: 'admin.nav.roleAssign', + icon: 'user-check', + to: '/admin/roles/assign', + permission: 'roles:assign', + }, + { name: 'admin.nav.policies', icon: 'lock', to: '/admin/policies', permission: 'roles:read' }, + { + name: 'admin.nav.auditLog', + icon: 'scroll', + to: '/admin/audit-log', + permission: 'audit_log:read', + }, + ], + }, +]; + +// ─── Helpers ─── + +function formatUptime(seconds: number): string { + const d = Math.floor(seconds / 86400); + const h = Math.floor((seconds % 86400) / 3600); + const m = Math.floor((seconds % 3600) / 60); + if (d > 0) return `${d}d ${h}h ${m}m`; + if (h > 0) return `${h}h ${m}m`; + return `${m}m`; +} + +// ─── Animated Stat Number ─── + +function AnimatedStat({ value, suffix }: { value: number; suffix?: string }) { + const animated = useAnimatedNumber(value); return ( - -
- {icon} -
-
-

- {title} -

-

{description}

-
-
- -
- + + {Math.round(animated).toLocaleString()} + {suffix} + ); } -function GroupSection({ group }: { group: AdminGroup }) { +// ─── Stats Bar ─── + +interface StatsBarProps { + systemInfo: SystemInfo | null; + dashboardStats: DashboardStats | null; + loading: boolean; +} + +const StatsBar = memo(function StatsBar({ systemInfo, dashboardStats, loading }: StatsBarProps) { + const { t } = useTranslation(); + + const stats = useMemo(() => { + const uptime = systemInfo?.uptime_seconds ?? 0; + const trial = dashboardStats?.subscriptions.trial ?? 0; + const paid = dashboardStats?.subscriptions.paid ?? 0; + const purchasedToday = dashboardStats?.subscriptions.purchased_today ?? 0; + + return [ + { + icon: , + label: t('admin.panel.statsUptime'), + value: uptime > 0 ? formatUptime(uptime) : '--', + colorClass: 'text-success-400 bg-success-400/10 border-success-400/20', + }, + { + icon: , + label: t('admin.panel.statsBot'), + value: systemInfo?.bot_version ?? '--', + colorClass: 'text-accent-400 bg-accent-400/10 border-accent-400/20', + }, + { + icon: , + label: t('admin.panel.statsCabinet'), + value: `v${CABINET_VERSION}`, + colorClass: 'text-accent-300 bg-accent-300/10 border-accent-300/20', + }, + { + icon: , + label: t('admin.panel.statsTrials'), + numericValue: trial, + colorClass: 'text-warning-400 bg-warning-400/10 border-warning-400/20', + }, + { + icon: , + label: t('admin.panel.statsPaid'), + numericValue: paid, + delta: purchasedToday > 0 ? `+${purchasedToday}` : undefined, + colorClass: 'text-success-400 bg-success-400/10 border-success-400/20', + }, + ]; + }, [systemInfo, dashboardStats, t]); + + return ( +
+ {stats.map((s, i) => ( +
+
+ {s.icon} +
+
+ + {'numericValue' in s && s.numericValue !== undefined ? ( + + ) : ( + {s.value} + )} + {s.delta && ( + + {s.delta} + + )} + + + {s.label} + {s.delta && ` · ${t('admin.panel.statsToday')}`} + +
+
+ ))} +
+ ); +}); + +// ─── Glass Card (Section) ─── + +interface GlassCardProps { + section: AdminSection; + index: number; + searchTerm: string; +} + +const GlassCard = memo(function GlassCard({ section, index, searchTerm }: GlassCardProps) { + const { t } = useTranslation(); const hasPermission = usePermissionStore((state) => state.hasPermission); - const visibleItems = group.items.filter((item) => hasPermission(item.permission)); + const [hoveredItem, setHoveredItem] = useState(null); + const cardRef = useRef(null); + const [tilt, setTilt] = useState({ x: 0, y: 0 }); + const tiltRef = useRef({ x: 0, y: 0 }); + const rafRef = useRef(0); + + useEffect(() => () => cancelAnimationFrame(rafRef.current), []); + + const onMouseMove = useCallback((e: React.MouseEvent) => { + if (!cardRef.current) return; + const rect = cardRef.current.getBoundingClientRect(); + tiltRef.current = { + x: ((e.clientY - rect.top) / rect.height - 0.5) * -2.5, + y: ((e.clientX - rect.left) / rect.width - 0.5) * 2.5, + }; + cancelAnimationFrame(rafRef.current); + rafRef.current = requestAnimationFrame(() => { + setTilt({ ...tiltRef.current }); + }); + }, []); + + const onMouseLeave = useCallback(() => { + cancelAnimationFrame(rafRef.current); + setTilt({ x: 0, y: 0 }); + }, []); + + const visibleItems = useMemo( + () => + section.items.filter((item) => { + if (!hasPermission(item.permission)) return false; + if (!searchTerm) return true; + return t(item.name).toLowerCase().includes(searchTerm.toLowerCase()); + }), + [section.items, hasPermission, searchTerm, t], + ); + + const highlightMatch = useCallback( + (text: string) => { + if (!searchTerm) return text; + const idx = text.toLowerCase().indexOf(searchTerm.toLowerCase()); + if (idx === -1) return text; + return ( + <> + {text.slice(0, idx)} + + {text.slice(idx, idx + searchTerm.length)} + + {text.slice(idx + searchTerm.length)} + + ); + }, + [searchTerm], + ); if (visibleItems.length === 0) return null; return ( -
- {/* Group Header */} -
-
-
{group.icon}
-

{group.title}

+
+ {/* Top glow line */} +
+ + {/* Header */} +
+
+ {/* Shine overlay */} +
+
+

+ {t(section.titleKey)} +

- {/* Group Items */} -
- {visibleItems.map((item) => ( - + {/* Items */} +
+ {visibleItems.map((item, i) => ( + setHoveredItem(i)} + onMouseLeave={() => setHoveredItem(null)} + style={{ + animation: `adminItemEnter 0.35s cubic-bezier(0.22, 1, 0.36, 1) ${index * 60 + i * 20}ms both`, + }} + > + {/* Icon */} +
+ {icons[item.icon]} +
+ + {/* Label */} + + {highlightMatch(t(item.name))} + + + {/* Chevron */} +
+ {icons.chevron} +
+ ))}
); -} +}); + +// ─── Main Component ─── export default function AdminPanel() { const { t } = useTranslation(); + const [search, setSearch] = useState(''); + const inputRef = useRef(null); + const { safeAreaInset, contentSafeAreaInset } = useTelegramSDK(); - const groups: AdminGroup[] = [ - { - id: 'analytics', - title: t('admin.groups.analytics'), - icon: , - gradient: 'from-success-500/10 to-success-500/5', - borderColor: 'border-success-500/20', - iconBg: 'bg-success-500/20', - iconColor: 'text-success-400', - items: [ - { - to: '/admin/dashboard', - icon: , - title: t('admin.nav.dashboard'), - description: t('admin.panel.dashboardDesc'), - permission: 'stats:read', - }, - { - to: '/admin/payments', - icon: , - title: t('admin.nav.payments'), - description: t('admin.panel.paymentsDesc'), - permission: 'payments:read', - }, - { - to: '/admin/traffic-usage', - icon: , - title: t('admin.nav.trafficUsage'), - description: t('admin.panel.trafficUsageDesc'), - permission: 'traffic:read', - }, - { - to: '/admin/sales-stats', - icon: , - title: t('admin.nav.salesStats'), - description: t('admin.panel.salesStatsDesc'), - permission: 'sales_stats:read', - }, - ], - }, - { - id: 'users', - title: t('admin.groups.users'), - icon: , - gradient: 'from-accent-500/10 to-accent-500/5', - borderColor: 'border-accent-500/20', - iconBg: 'bg-accent-500/20', - iconColor: 'text-accent-400', - items: [ - { - to: '/admin/users', - icon: , - title: t('admin.nav.users'), - description: t('admin.panel.usersDesc'), - permission: 'users:read', - }, - { - to: '/admin/tickets', - icon: , - title: t('admin.nav.tickets'), - description: t('admin.panel.ticketsDesc'), - permission: 'tickets:read', - }, - { - to: '/admin/ban-system', - icon: , - title: t('admin.nav.banSystem'), - description: t('admin.panel.banSystemDesc'), - permission: 'ban_system:read', - }, - ], - }, - { - id: 'tariffs', - title: t('admin.groups.tariffs'), - icon: , - gradient: 'from-warning-500/10 to-warning-500/5', - borderColor: 'border-warning-500/20', - iconBg: 'bg-warning-500/20', - iconColor: 'text-warning-400', - items: [ - { - to: '/admin/tariffs', - icon: , - title: t('admin.nav.tariffs'), - description: t('admin.panel.tariffsDesc'), - permission: 'tariffs:read', - }, - { - to: '/admin/promocodes', - icon: , - title: t('admin.nav.promocodes'), - description: t('admin.panel.promocodesDesc'), - permission: 'promocodes:read', - }, - { - to: '/admin/promo-groups', - icon: , - title: t('admin.nav.promoGroups'), - description: t('admin.panel.promoGroupsDesc'), - permission: 'promo_groups:read', - }, - { - to: '/admin/promo-offers', - icon: , - title: t('admin.nav.promoOffers'), - description: t('admin.panel.promoOffersDesc'), - permission: 'promo_offers:read', - }, - { - to: '/admin/payment-methods', - icon: , - title: t('admin.nav.paymentMethods'), - description: t('admin.panel.paymentMethodsDesc'), - permission: 'payment_methods:read', - }, - { - to: '/admin/landings', - icon: , - title: t('admin.nav.landings'), - description: t('admin.panel.landingsDesc'), - permission: 'landings:read', - }, - ], - }, - { - id: 'marketing', - title: t('admin.groups.marketing'), - icon: , - gradient: 'from-error-500/10 to-error-500/5', - borderColor: 'border-error-500/20', - iconBg: 'bg-error-500/20', - iconColor: 'text-error-400', - items: [ - { - to: '/admin/news', - icon: , - title: t('admin.nav.news'), - description: t('admin.panel.newsDesc'), - permission: 'news:read', - }, - { - to: '/admin/campaigns', - icon: , - title: t('admin.nav.campaigns'), - description: t('admin.panel.campaignsDesc'), - permission: 'campaigns:read', - }, - { - to: '/admin/broadcasts', - icon: , - title: t('admin.nav.broadcasts'), - description: t('admin.panel.broadcastsDesc'), - permission: 'broadcasts:read', - }, - { - to: '/admin/pinned-messages', - icon: , - title: t('admin.nav.pinnedMessages'), - description: t('admin.panel.pinnedMessagesDesc'), - permission: 'pinned_messages:read', - }, - { - to: '/admin/wheel', - icon: , - title: t('admin.nav.wheel'), - description: t('admin.panel.wheelDesc'), - permission: 'wheel:read', - }, - { - to: '/admin/partners', - icon: , - title: t('admin.nav.partners'), - description: t('admin.panel.partnersDesc'), - permission: 'partners:read', - }, - { - to: '/admin/withdrawals', - icon: , - title: t('admin.nav.withdrawals'), - description: t('admin.panel.withdrawalsDesc'), - permission: 'withdrawals:read', - }, - { - to: '/admin/referral-network', - icon: , - title: t('admin.nav.referralNetwork'), - description: t('admin.panel.referralNetworkDesc'), - permission: 'stats:read', - }, - ], - }, - { - id: 'system', - title: t('admin.groups.system'), - icon: , - gradient: 'from-violet-500/10 to-violet-500/5', - borderColor: 'border-violet-500/20', - iconBg: 'bg-violet-500/20', - iconColor: 'text-violet-400', - items: [ - { - to: '/admin/channel-subscriptions', - icon: , - title: t('admin.nav.channelSubscriptions'), - description: t('admin.panel.channelSubscriptionsDesc'), - permission: 'channels:read', - }, - { - to: '/admin/settings', - icon: , - title: t('admin.nav.settings'), - description: t('admin.panel.settingsDesc'), - permission: 'settings:read', - }, - { - to: '/admin/apps', - icon: , - title: t('admin.nav.apps'), - description: t('admin.panel.appsDesc'), - permission: 'apps:read', - }, - { - to: '/admin/servers', - icon: , - title: t('admin.nav.servers'), - description: t('admin.panel.serversDesc'), - permission: 'servers:read', - }, - { - to: '/admin/remnawave', - icon: , - title: t('admin.nav.remnawave'), - description: t('admin.panel.remnawaveDesc'), - permission: 'remnawave:read', - }, - { - to: '/admin/email-templates', - icon: , - title: t('admin.nav.emailTemplates'), - description: t('admin.panel.emailTemplatesDesc'), - permission: 'email_templates:read', - }, - { - to: '/admin/updates', - icon: , - title: t('admin.nav.updates'), - description: t('admin.panel.updatesDesc'), - permission: 'updates:read', - }, - ], - }, - { - id: 'security', - title: t('admin.groups.security'), - icon: , - gradient: 'from-red-500/10 to-orange-500/5', - borderColor: 'border-red-500/20', - iconBg: 'bg-red-500/20', - iconColor: 'text-red-400', - items: [ - { - to: '/admin/roles', - icon: , - title: t('admin.nav.roles'), - description: t('admin.panel.rolesDesc'), - permission: 'roles:read', - }, - { - to: '/admin/roles/assign', - icon: , - title: t('admin.nav.roleAssign'), - description: t('admin.panel.roleAssignDesc'), - permission: 'roles:assign', - }, - { - to: '/admin/policies', - icon: , - title: t('admin.nav.policies'), - description: t('admin.panel.policiesDesc'), - permission: 'roles:read', - }, - { - to: '/admin/audit-log', - icon: , - title: t('admin.nav.auditLog'), - description: t('admin.panel.auditLogDesc'), - permission: 'audit_log:read', - }, - ], - }, - ]; + const [systemInfo, setSystemInfo] = useState(null); + const [dashboardStats, setDashboardStats] = useState(null); + const [loading, setLoading] = useState(true); + + const safeTop = Math.max(safeAreaInset.top, contentSafeAreaInset.top); + const safeBottom = Math.max(safeAreaInset.bottom, contentSafeAreaInset.bottom); + + // Fetch stats + useEffect(() => { + let cancelled = false; + const fetchData = async () => { + try { + const [sysInfo, stats] = await Promise.all([ + statsApi.getSystemInfo(), + statsApi.getDashboardStats(), + ]); + if (!cancelled) { + setSystemInfo(sysInfo); + setDashboardStats(stats); + setLoading(false); + } + } catch { + if (!cancelled) setLoading(false); + } + }; + fetchData(); + const interval = setInterval(fetchData, 60_000); + return () => { + cancelled = true; + clearInterval(interval); + }; + }, []); + + // Keyboard shortcuts: Cmd+K to focus search, Escape to clear + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); + inputRef.current?.focus(); + } + if (e.key === 'Escape') { + setSearch(''); + inputRef.current?.blur(); + } + }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, []); + + // Track which sections have matching items (keeps original section refs for memo stability) + const visibleSectionIds = useMemo(() => { + if (!search.trim()) return null; // null = show all + const lower = search.toLowerCase(); + return new Set( + sections + .filter((s) => s.items.some((item) => t(item.name).toLowerCase().includes(lower))) + .map((s) => s.id), + ); + }, [search, t]); return ( -
- {/* Header */} -
-

{t('admin.panel.title')}

-

{t('admin.panel.subtitle')}

-
+
+
0 ? `${safeTop}px` : 'env(safe-area-inset-top, 0px)', + paddingBottom: safeBottom > 0 ? `${safeBottom}px` : 'env(safe-area-inset-bottom, 0px)', + }} + > + {/* Stats Bar */} +
+ +
- {/* Groups Grid */} -
- {groups.map((group) => ( - - ))} + {/* Mobile: compact 2-column stats */} +
+ {[ + { + icon: , + label: t('admin.panel.statsUptime'), + value: systemInfo?.uptime_seconds ? formatUptime(systemInfo.uptime_seconds) : '--', + cls: 'text-success-400', + }, + { + icon: , + label: t('admin.panel.statsBot'), + value: systemInfo?.bot_version ?? '--', + cls: 'text-accent-400', + }, + { + icon: , + label: t('admin.panel.statsTrials'), + value: dashboardStats?.subscriptions.trial?.toLocaleString() ?? '--', + cls: 'text-warning-400', + }, + { + icon: , + label: t('admin.panel.statsPaid'), + value: dashboardStats?.subscriptions.paid?.toLocaleString() ?? '--', + delta: + (dashboardStats?.subscriptions.purchased_today ?? 0) > 0 + ? `+${dashboardStats?.subscriptions.purchased_today}` + : undefined, + cls: 'text-success-400', + }, + ].map((s, i) => ( +
+
{s.icon}
+
+ + {s.value} + {'delta' in s && s.delta && ( + + {s.delta} + + )} + + + {s.label} + +
+
+ ))} +
+ + {/* Hero + Search */} +
+

+ {t('admin.panel.title')} +

+
+
+ {t('admin.panel.statsOnline')} +
+ {/* Search */} +
+
+ {icons.search} +
+ setSearch(e.target.value)} + placeholder={t('admin.panel.searchPlaceholder')} + aria-label={t('admin.panel.searchPlaceholder')} + className="w-full rounded-xl border border-dark-700/50 bg-dark-800/40 py-2 pl-8 pr-16 font-sans text-xs text-dark-100 outline-none backdrop-blur-lg transition-all placeholder:text-dark-500 focus:border-accent-500/40 focus:shadow-[0_0_0_3px_rgba(var(--color-accent-500),0.08)] light:border-champagne-300/50 light:bg-champagne-100/60 light:text-champagne-900 light:placeholder:text-champagne-500 light:focus:border-accent-500/40" + /> + {search && ( + + )} + +
+
+ + {/* Grid */} +
+ {visibleSectionIds === null || visibleSectionIds.size > 0 ? ( +
+ {sections + .filter((s) => !visibleSectionIds || visibleSectionIds.has(s.id)) + .map((section, i) => ( + + ))} +
+ ) : ( +
+
+ {icons.search} +
+

+ {t('admin.panel.searchEmpty')} +

+

+ {t('admin.panel.searchEmptyHint')} +

+
+ )} +
); diff --git a/src/pages/AdminPromoOfferTemplateEdit.tsx b/src/pages/AdminPromoOfferTemplateEdit.tsx index 21c7c0b..a3a4851 100644 --- a/src/pages/AdminPromoOfferTemplateEdit.tsx +++ b/src/pages/AdminPromoOfferTemplateEdit.tsx @@ -8,6 +8,7 @@ import { OFFER_TYPE_CONFIG, OfferType, } from '../api/promoOffers'; +import { serversApi } from '../api/servers'; import { AdminBackButton } from '../components/admin'; import { createNumberInputHandler, toNumber } from '../utils/inputHelpers'; @@ -31,6 +32,14 @@ export default function AdminPromoOfferTemplateEdit() { const [testDurationHours, setTestDurationHours] = useState(0); const [isActive, setIsActive] = useState(true); const [isTestAccess, setIsTestAccess] = useState(false); + const [selectedSquadUuids, setSelectedSquadUuids] = useState([]); + + // Fetch available servers for test_access squad selection + const { data: serversData } = useQuery({ + queryKey: ['admin-servers-for-promo'], + queryFn: () => serversApi.getServers(true), + enabled: isTestAccess, + }); // Query template const { data: templatesData, isLoading } = useQuery({ @@ -52,6 +61,7 @@ export default function AdminPromoOfferTemplateEdit() { setTestDurationHours(template.test_duration_hours || 0); setIsActive(template.is_active); setIsTestAccess(template.offer_type === 'test_access'); + setSelectedSquadUuids(template.test_squad_uuids || []); } }, [template]); @@ -78,6 +88,7 @@ export default function AdminPromoOfferTemplateEdit() { const discountHours = toNumber(activeDiscountHours); if (isTestAccess) { data.test_duration_hours = testHours > 0 ? testHours : undefined; + data.test_squad_uuids = selectedSquadUuids; } else { data.active_discount_hours = discountHours > 0 ? discountHours : undefined; } @@ -201,22 +212,70 @@ export default function AdminPromoOfferTemplateEdit() {
{isTestAccess ? ( -
- - -

- {t('admin.promoOffers.form.defaultZero')} -

-
+ <> +
+ + +

+ {t('admin.promoOffers.form.defaultZero')} +

+
+
+ + {serversData?.servers && serversData.servers.length > 0 ? ( +
+ {serversData.servers.map((server) => ( + + ))} +
+ ) : ( +

+ {t('admin.promoOffers.form.noServers', 'Нет доступных серверов')} +

+ )} + {selectedSquadUuids.length === 0 && ( +

+ {t( + 'admin.promoOffers.form.selectSquadHint', + 'Выберите хотя бы один сервер для тестового доступа', + )} +

+ )} +
+ ) : (
)} + {type === 'trial_subscription' && ( +
+ + + {!tariffId && !trialTariff && ( +
+ {t( + 'admin.promocodes.form.noTrialTariffHint', + 'Выберите тариф или отметьте тариф как «доступен для триала» в настройках.', + )} +
+ )} +
+ )} + {type === 'promo_group' && (
@@ -355,15 +348,9 @@ function OverviewTab({ stats, isLoading, onRefresh }: OverviewTabProps) { ); } - // Use (total - available) instead of raw "used" to exclude disk cache/buffers - // This matches what htop/free show as actual application memory usage - const memoryActualUsed = - stats.server_info.memory_available > 0 - ? stats.server_info.memory_total - stats.server_info.memory_available - : stats.server_info.memory_used; const memoryUsedPercent = stats.server_info.memory_total > 0 - ? Math.round((memoryActualUsed / stats.server_info.memory_total) * 100) + ? Math.round((stats.server_info.memory_used / stats.server_info.memory_total) * 100) : 0; return ( @@ -405,24 +392,24 @@ function OverviewTab({ stats, isLoading, onRefresh }: OverviewTabProps) { {/* Bandwidth */}

- {t('admin.remnawave.overview.bandwidth', 'Realtime Bandwidth')} + {t('admin.remnawave.overview.bandwidth', 'Inbound Traffic')}

↓} color="green" /> ↑} color="blue" /> ⇅} color="purple" /> @@ -437,14 +424,14 @@ function OverviewTab({ stats, isLoading, onRefresh }: OverviewTabProps) {
⚡} color="accent" /> 💾} color={memoryUsedPercent > 80 ? 'red' : memoryUsedPercent > 60 ? 'orange' : 'green'} /> @@ -865,7 +852,138 @@ function SyncTab({ ); } -type TabType = 'overview' | 'nodes' | 'squads' | 'sync'; +interface TrafficTabProps { + data: NodeRealtimeStats[] | undefined; + isLoading: boolean; + onRefresh: () => void; +} + +function TrafficTab({ data, isLoading, onRefresh }: TrafficTabProps) { + const { t } = useTranslation(); + + if (isLoading) { + return ( +
+
+
+ ); + } + + if (!data || data.length === 0) { + return ( +
+

+ {t('admin.remnawave.traffic.noData', 'No traffic data available')} +

+ +
+ ); + } + + const totalDownload = data.reduce((acc, n) => acc + n.downloadBytes, 0); + const totalUpload = data.reduce((acc, n) => acc + n.uploadBytes, 0); + + return ( +
+ {/* Totals */} +
+ ↓} + color="green" + /> + ↑} + color="blue" + /> + ⇅} + color="purple" + /> +
+ + {/* Per-node inbound breakdown */} + {data.map((node) => ( +
+
+
+ {node.countryEmoji && {node.countryEmoji}} +

{node.nodeName}

+ {node.providerName && ( + + {node.providerName} + + )} + + {node.usersOnline} {t('admin.remnawave.traffic.online', 'online')} + +
+ {formatBytes(node.totalBytes)} +
+ + {(node.inbounds?.length ?? 0) > 0 && ( +
+

+ {t('admin.remnawave.traffic.inbounds', 'Inbounds')} +

+ {[...(node.inbounds ?? [])] + .sort((a, b) => b.totalBytes - a.totalBytes) + .map((ib) => ( +
+ {ib.tag} +
+ ↓ {formatBytes(ib.downloadBytes)} + ↑ {formatBytes(ib.uploadBytes)} + + {formatBytes(ib.totalBytes)} + +
+
+ ))} +
+ )} + + {(node.outbounds?.length ?? 0) > 0 && ( +
+

+ {t('admin.remnawave.traffic.outbounds', 'Outbounds')} +

+ {[...(node.outbounds ?? [])] + .sort((a, b) => b.totalBytes - a.totalBytes) + .map((ob) => ( +
+ {ob.tag} +
+ ↓ {formatBytes(ob.downloadBytes)} + ↑ {formatBytes(ob.uploadBytes)} + + {formatBytes(ob.totalBytes)} + +
+
+ ))} +
+ )} +
+ ))} +
+ ); +} + +type TabType = 'overview' | 'nodes' | 'traffic' | 'squads' | 'sync'; export default function AdminRemnawave() { const { t } = useTranslation(); @@ -918,6 +1036,17 @@ export default function AdminRemnawave() { enabled: activeTab === 'squads', }); + const { + data: realtimeData, + isLoading: isLoadingRealtime, + refetch: refetchRealtime, + } = useQuery({ + queryKey: ['admin-remnawave-realtime'], + queryFn: adminRemnawaveApi.getNodesRealtime, + enabled: activeTab === 'traffic', + refetchInterval: 10000, + }); + const { data: autoSyncStatus, isLoading: isLoadingAutoSync } = useQuery({ queryKey: ['admin-remnawave-autosync'], queryFn: adminRemnawaveApi.getAutoSyncStatus, @@ -992,6 +1121,11 @@ export default function AdminRemnawave() { icon: , }, { id: 'nodes' as const, label: t('admin.remnawave.tabs.nodes', 'Nodes'), icon: }, + { + id: 'traffic' as const, + label: t('admin.remnawave.tabs.traffic', 'Traffic'), + icon: , + }, { id: 'squads' as const, label: t('admin.remnawave.tabs.squads', 'Squads'), @@ -1089,6 +1223,14 @@ export default function AdminRemnawave() { /> )} + {activeTab === 'traffic' && ( + refetchRealtime()} + /> + )} + {activeTab === 'squads' && ( ( @@ -33,17 +30,18 @@ const BackIcon = () => ( ); -// Find section ID by category key -function findSectionByCategory(categoryKey: string): string | null { - for (const section of MENU_SECTIONS) { - for (const item of section.items) { - if (item.categories?.includes(categoryKey)) { - return item.id; - } - } - } - return null; -} +// ChevronRight for breadcrumbs +const ChevronRightIcon = () => ( + + + +); export default function AdminSettings() { const { t } = useTranslation(); @@ -73,23 +71,42 @@ export default function AdminSettings() { queryFn: () => adminSettingsApi.getSettings(), }); - // Get current menu item configuration - const currentMenuItem = useMemo(() => { - for (const section of MENU_SECTIONS) { - const item = section.items.find((i: MenuItem) => i.id === activeSection); - if (item) return item; + // Find active tree info (group + child for tree sub-items) + // No useMemo needed — SETTINGS_TREE is a static constant, iteration is trivial + let activeTreeInfo: { + group: (typeof SETTINGS_TREE.groups)[number]; + child: (typeof SETTINGS_TREE.groups)[number]['children'][number]; + } | null = null; + for (const group of SETTINGS_TREE.groups) { + const child = group.children.find((c) => c.id === activeSection); + if (child) { + activeTreeInfo = { group, child }; + break; } - return null; - }, [activeSection]); + } - // Get categories for current section + // Settings that require SALES_MODE=tariffs to be visible + const TARIFF_MODE_SETTINGS = ['MULTI_TARIFF_ENABLED', 'MAX_ACTIVE_SUBSCRIPTIONS']; + + // Check if tariffs mode is active + const isTariffsMode = useMemo(() => { + if (!allSettings || !Array.isArray(allSettings)) return false; + const salesMode = allSettings.find((s: SettingDefinition) => s.key === 'SALES_MODE'); + return salesMode?.current === 'tariffs'; + }, [allSettings]); + + // Get categories for current sub-item const currentCategories = useMemo(() => { - if (!currentMenuItem?.categories || !allSettings || !Array.isArray(allSettings)) return []; + if (!activeTreeInfo || !allSettings || !Array.isArray(allSettings)) return []; + const categoryKeys = activeTreeInfo.child.categories; const categoryMap = new Map(); for (const setting of allSettings) { - if (currentMenuItem.categories.includes(setting.category.key)) { + if (categoryKeys.includes(setting.category.key)) { + // Hide tariff-dependent settings when not in tariffs mode + if (!isTariffsMode && TARIFF_MODE_SETTINGS.includes(setting.key)) continue; + if (!categoryMap.has(setting.category.key)) { categoryMap.set(setting.category.key, []); } @@ -102,7 +119,8 @@ export default function AdminSettings() { label: t(`admin.settings.categories.${key}`, key), settings, })); - }, [currentMenuItem, allSettings, t]); + // eslint-disable-next-line react-hooks/exhaustive-deps -- activeTreeInfo derived from activeSection + }, [activeSection, allSettings, isTariffsMode, t]); // Filter settings for search - GLOBAL search across all settings const filteredSettings = useMemo(() => { @@ -112,27 +130,20 @@ export default function AdminSettings() { if (!q) return []; return allSettings.filter((s: SettingDefinition) => { - // Search by key + // Hide tariff-dependent settings when not in tariffs mode + if (!isTariffsMode && TARIFF_MODE_SETTINGS.includes(s.key)) return false; + if (s.key.toLowerCase().includes(q)) return true; - - // Search by original name if (s.name?.toLowerCase().includes(q)) return true; - - // Search by translated name const formattedKey = formatSettingKey(s.name || s.key); const translatedName = t(`admin.settings.settingNames.${formattedKey}`, formattedKey); if (translatedName.toLowerCase().includes(q)) return true; - - // Search by description if (s.hint?.description?.toLowerCase().includes(q)) return true; - - // Search by category const categoryLabel = t(`admin.settings.categories.${s.category.key}`, s.category.key); if (categoryLabel.toLowerCase().includes(q)) return true; - return false; }); - }, [allSettings, searchQuery, t]); + }, [allSettings, searchQuery, isTariffsMode, t]); // Favorite settings const favoriteSettings = useMemo(() => { @@ -140,19 +151,43 @@ export default function AdminSettings() { return allSettings.filter((s: SettingDefinition) => favorites.includes(s.key)); }, [allSettings, favorites]); + // Count of modified settings + const modifiedCount = useMemo(() => { + if (!allSettings || !Array.isArray(allSettings)) return 0; + return allSettings.filter((s: SettingDefinition) => s.has_override).length; + }, [allSettings]); + + // Total settings count + const totalCount = useMemo(() => { + if (!allSettings || !Array.isArray(allSettings)) return 0; + return allSettings.length; + }, [allSettings]); + // Handle setting selection from autocomplete const handleSelectSetting = useCallback( (setting: SettingDefinition) => { - const sectionId = findSectionByCategory(setting.category.key); - if (sectionId) { - setActiveSection(sectionId); + const location = findTreeLocation(setting.category.key); + if (location) { + setActiveSection(location.subItemId); } - // Set search to setting key to filter to just this setting setSearchQuery(setting.key); }, [setActiveSection, setSearchQuery], ); + // Get the display title for the current section + const sectionTitle = useMemo(() => { + // Special items + const specialItem = SETTINGS_TREE.specialItems.find((item) => item.id === activeSection); + if (specialItem) return t(`admin.settings.${specialItem.id}`); + + // Tree sub-items + if (activeTreeInfo) return t(`admin.settings.tree.${activeTreeInfo.child.id}`); + + return t('admin.settings.title'); + // eslint-disable-next-line react-hooks/exhaustive-deps -- activeTreeInfo derived from activeSection + }, [activeSection, t]); + // Render content based on active section const renderContent = () => { // If searching, always show search results regardless of active section @@ -186,17 +221,7 @@ export default function AdminSettings() { /> ); default: - if ( - [ - 'payments', - 'subscriptions', - 'interface', - 'notifications', - 'database', - 'system', - 'users', - ].includes(activeSection) - ) { + if (activeTreeInfo) { return ( ); } + // Unknown section — fallback to branding + setActiveSection('branding'); return null; } }; @@ -214,7 +241,7 @@ export default function AdminSettings() { return ( <> {/* Mobile Layout */} -
+
{/* Fixed Sidebar */} -
+
{/* Show back button only on web, not in Telegram Mini App */} @@ -241,6 +268,7 @@ export default function AdminSettings() { @@ -248,67 +276,52 @@ export default function AdminSettings() {

{t('admin.settings.title')}

- +
{/* Scrollable Content */}
+ {/* Breadcrumb for tree sub-items */} + {activeTreeInfo && !searchQuery.trim() && ( +
+ + + + {t(`admin.settings.tree.${activeTreeInfo.child.id}`)} + +
+ )} + + {/* Title + count badges */}
-

- {t(`admin.settings.${activeSection}`)} -

-
- +

{sectionTitle}

+ {totalCount > 0 && !searchQuery.trim() && activeTreeInfo && ( +
+ + {t('admin.settings.totalCount', { count: totalCount })} + + {modifiedCount > 0 && ( + + {t('admin.settings.modifiedCount', { count: modifiedCount })} + + )} +
+ )}
+ {renderContent()}
diff --git a/src/pages/AdminTariffCreate.tsx b/src/pages/AdminTariffCreate.tsx index 5c0f1e6..798c7dc 100644 --- a/src/pages/AdminTariffCreate.tsx +++ b/src/pages/AdminTariffCreate.tsx @@ -220,7 +220,7 @@ export default function AdminTariffCreate() { traffic_limit_gb: toNumber(trafficLimitGb, 100), device_limit: toNumber(deviceLimit, 1), device_price_kopeks: - toNumber(devicePriceKopeks) > 0 ? toNumber(devicePriceKopeks) : undefined, + toNumber(devicePriceKopeks) >= 0 ? toNumber(devicePriceKopeks) : undefined, max_device_limit: toNumber(maxDeviceLimit) > 0 ? toNumber(maxDeviceLimit) : undefined, tier_level: toNumber(tierLevel, 1), period_prices: isDaily ? [] : periodPrices.filter((p) => p.price_kopeks >= 0), @@ -912,7 +912,7 @@ export default function AdminTariffCreate() { onClick={() => { const gb = toNumber(newPackageGb, 0); const price = toNumber(newPackagePrice, 0); - if (gb > 0 && price > 0 && !trafficTopupPackages[String(gb)]) { + if (gb > 0 && price >= 0 && !trafficTopupPackages[String(gb)]) { setTrafficTopupPackages((prev) => ({ ...prev, [String(gb)]: price * 100, @@ -1029,6 +1029,11 @@ export default function AdminTariffCreate() { { value: 'DAY', labelKey: 'admin.tariffs.resetModeDaily', emoji: '📅' }, { value: 'WEEK', labelKey: 'admin.tariffs.resetModeWeekly', emoji: '📆' }, { value: 'MONTH', labelKey: 'admin.tariffs.resetModeMonthly', emoji: '🗓️' }, + { + value: 'MONTH_ROLLING', + labelKey: 'admin.tariffs.resetModeMonthRolling', + emoji: '🔄', + }, { value: 'NO_RESET', labelKey: 'admin.tariffs.resetModeNever', emoji: '🚫' }, ].map((option) => (
@@ -1356,15 +1588,143 @@ export default function AdminUserDetail() { {/* Subscription Tab */} {activeTab === 'subscription' && (
- {user.subscription ? ( + {/* Multi-subscription: Level 1 — subscription list */} + {userSubscriptions.length > 1 && !subscriptionDetailView && ( <> +
+ {userSubscriptions.map((sub) => ( + + ))} +
+ + {/* Create new subscription — at list level */} + {hasPermission('users:subscription') && ( +
+
+ {t('admin.users.detail.subscription.createNew', 'Создать подписку')} +
+
+ + + +
+
+ )} + + )} + + {/* Level 2 — subscription detail (or single subscription) */} + {(subscriptionDetailView || userSubscriptions.length <= 1) && selectedSub ? ( + <> + {/* Back to list (multi-subscription) */} + {subscriptionDetailView && userSubscriptions.length > 1 && ( + + )} + {/* Current subscription */}
{t('admin.users.detail.subscription.current')} + {userSubscriptions.length > 1 && ( + #{selectedSub.id} + )} - +
@@ -1372,7 +1732,7 @@ export default function AdminUserDetail() { {t('admin.users.detail.subscription.tariff')}
- {user.subscription.tariff_name || + {selectedSub.tariff_name || t('admin.users.detail.subscription.notSpecified')}
@@ -1380,7 +1740,7 @@ export default function AdminUserDetail() {
{t('admin.users.detail.subscription.validUntil')}
-
{formatDate(user.subscription.end_date)}
+
{formatDate(selectedSub.end_date)}
@@ -1389,8 +1749,8 @@ export default function AdminUserDetail() {
{panelInfo?.found ? (panelInfo.used_traffic_bytes / (1024 * 1024 * 1024)).toFixed(1) - : user.subscription.traffic_used_gb.toFixed(1)}{' '} - / {user.subscription.traffic_limit_gb} {t('common.units.gb')} + : selectedSub.traffic_used_gb.toFixed(1)}{' '} + / {selectedSub.traffic_limit_gb} {t('common.units.gb')}
@@ -1399,21 +1759,21 @@ export default function AdminUserDetail() {
- {user.subscription.device_limit} + {selectedSub.device_limit}
{/* Traffic Packages */} - {user.subscription.traffic_purchases && - user.subscription.traffic_purchases.length > 0 && ( -
-
- - {t('admin.users.detail.subscription.trafficPackages')} - {user.subscription.purchased_traffic_gb > 0 && ( - - ({user.subscription.purchased_traffic_gb} {t('common.units.gb')}) - - )} - -
-
- {user.subscription.traffic_purchases.map((tp) => ( -
-
-
- - {tp.traffic_gb} {t('common.units.gb')} - - {tp.is_expired ? ( - - {t('admin.users.detail.subscription.expired')} - - ) : ( - - {tp.days_remaining}{' '} - {t('admin.users.detail.subscription.daysLeft')} - - )} -
-
- {!tp.is_expired && ( - - )} -
- ))} -
+ {selectedSub.traffic_purchases && selectedSub.traffic_purchases.length > 0 && ( +
+
+ + {t('admin.users.detail.subscription.trafficPackages')} + {selectedSub.purchased_traffic_gb > 0 && ( + + ({selectedSub.purchased_traffic_gb} {t('common.units.gb')}) + + )} +
- )} +
+ {selectedSub.traffic_purchases.map((tp) => ( +
+
+
+ + {tp.traffic_gb} {t('common.units.gb')} + + {tp.is_expired ? ( + + {t('admin.users.detail.subscription.expired')} + + ) : ( + + {tp.days_remaining}{' '} + {t('admin.users.detail.subscription.daysLeft')} + + )} +
+
+ {!tp.is_expired && ( + + )} +
+ ))} +
+
+ )} {/* Add Traffic */} {currentTariff && @@ -1545,9 +1904,11 @@ export default function AdminUserDetail() { - + {userSubscriptions.length <= 1 && ( + + )} @@ -1602,10 +1963,18 @@ export default function AdminUserDetail() {
)} - ) : hasPermission('users:subscription') ? ( + ) : null} + + {/* Create new subscription — only for single-sub users or no subs */} + {hasPermission('users:subscription') && userSubscriptions.length <= 1 && (
-
- {t('admin.users.detail.subscription.noActive')} + {userSubscriptions.length === 0 && ( +
+ {t('admin.users.detail.subscription.noActive')} +
+ )} +
+ {t('admin.users.detail.subscription.createNew', 'Создать подписку')}
- ) : ( -
-
- {t('admin.users.detail.subscription.noActive')} -
-
)} - {/* Panel Info */} - {panelInfoLoading ? ( -
-
-
- ) : panelInfo && !panelInfo.found ? ( -
- {t('admin.users.detail.panelNotFound')} -
- ) : panelInfo && panelInfo.found ? ( + {/* Panel Info, Traffic, Devices — only inside subscription detail */} + {(subscriptionDetailView || userSubscriptions.length <= 1) && ( <> - {/* Links */} - {(panelInfo.subscription_url || panelInfo.happ_link) && ( -
-
- {t('admin.users.detail.subscriptionUrl')} / {t('admin.users.detail.happLink')} -
-
- {panelInfo.subscription_url && ( - - )} - {panelInfo.happ_link && ( - - )} -
+ {panelInfoLoading ? ( +
+
- )} - - {/* Config */} - {(panelInfo.trojan_password || panelInfo.vless_uuid || panelInfo.ss_password) && ( -
-
- {t('admin.users.detail.panelConfig')} -
-
- {panelInfo.trojan_password && ( - - )} - {panelInfo.vless_uuid && ( - - )} - {panelInfo.ss_password && ( - - )} -
+ ) : panelInfo && !panelInfo.found ? ( +
+ {t('admin.users.detail.panelNotFound')}
- )} - - {/* Connection info */} -
-
-
-
- {t('admin.users.detail.firstConnected')} -
-
- {formatDate(panelInfo.first_connected_at)} -
-
-
-
- {t('admin.users.detail.lastOnline')} -
-
{formatDate(panelInfo.online_at)}
-
- {panelInfo.last_connected_node_name && ( -
-
- {t('admin.users.detail.lastNode')} + ) : panelInfo && panelInfo.found ? ( + <> + {/* Links */} + {(panelInfo.subscription_url || panelInfo.happ_link) && ( +
+
+ {t('admin.users.detail.subscriptionUrl')} /{' '} + {t('admin.users.detail.happLink')}
-
- {panelInfo.last_connected_node_name} +
+ {panelInfo.subscription_url && ( + + )} + {panelInfo.happ_link && ( + + )}
)} -
-
- {/* Live traffic */} -
-
- {t('admin.users.detail.liveTraffic')} -
-
-
- - {formatBytes(panelInfo.used_traffic_bytes)} - - - {panelInfo.traffic_limit_bytes > 0 - ? formatBytes(panelInfo.traffic_limit_bytes) - : '∞'} - -
-
-
0 - ? `${Math.min(100, (panelInfo.used_traffic_bytes / panelInfo.traffic_limit_bytes) * 100)}%` - : '0%', - }} - /> -
-
-
- {t('admin.users.detail.lifetime')}:{' '} - {formatBytes(panelInfo.lifetime_used_traffic_bytes)} -
-
+ {/* Config */} + {(panelInfo.trojan_password || + panelInfo.vless_uuid || + panelInfo.ss_password) && ( +
+
+ {t('admin.users.detail.panelConfig')} +
+
+ {panelInfo.trojan_password && ( + + )} + {panelInfo.vless_uuid && ( + + )} + {panelInfo.ss_password && ( + + )} +
+
+ )} - {/* Node usage */} + {/* Connection info */} +
+
+
+
+ {t('admin.users.detail.firstConnected')} +
+
+ {formatDate(panelInfo.first_connected_at)} +
+
+
+
+ {t('admin.users.detail.lastOnline')} +
+
+ {formatDate(panelInfo.online_at)} +
+
+ {panelInfo.last_connected_node_name && ( +
+
+ {t('admin.users.detail.lastNode')} +
+
+ {panelInfo.last_connected_node_name} +
+
+ )} +
+
+ + {/* Live traffic */} +
+
+ {t('admin.users.detail.liveTraffic')} +
+
+
+ + {formatBytes(panelInfo.used_traffic_bytes)} + + + {panelInfo.traffic_limit_bytes > 0 + ? formatBytes(panelInfo.traffic_limit_bytes) + : '∞'} + +
+
+
0 + ? `${Math.min(100, (panelInfo.used_traffic_bytes / panelInfo.traffic_limit_bytes) * 100)}%` + : '0%', + }} + /> +
+
+
+ {t('admin.users.detail.lifetime')}:{' '} + {formatBytes(panelInfo.lifetime_used_traffic_bytes)} +
+
+ + {/* Node usage */} +
+
+ + {t('admin.users.detail.nodeUsage')} + +
+
+ {[1, 3, 7, 14, 30].map((d) => ( + + ))} +
+ +
+
+ {nodeUsageForPeriod.length > 0 ? ( +
+ {nodeUsageForPeriod.map((item) => { + const maxBytes = nodeUsageForPeriod[0].total_bytes; + const pct = maxBytes > 0 ? (item.total_bytes / maxBytes) * 100 : 0; + return ( +
+
+ + {item.country_code && ( + + {getCountryFlag(item.country_code)} + + )} + {item.node_name} + + + {formatBytes(item.total_bytes)} + +
+
+
+
+
+ ); + })} +
+ ) : ( +
-
+ )} +
+ + ) : null} + + {/* Devices */}
- {t('admin.users.detail.nodeUsage')} + {t('admin.users.detail.devices.title')} ({devicesTotal}/{deviceLimit})
-
- {[1, 3, 7, 14, 30].map((d) => ( - - ))} -
+ {devices.length > 0 && ( + + )}
- {nodeUsageForPeriod.length > 0 ? ( + {devicesLoading ? ( +
+
+
+ ) : devices.length > 0 ? (
- {nodeUsageForPeriod.map((item) => { - const maxBytes = nodeUsageForPeriod[0].total_bytes; - const pct = maxBytes > 0 ? (item.total_bytes / maxBytes) * 100 : 0; - return ( -
-
- - {item.country_code && ( - {getCountryFlag(item.country_code)} - )} - {item.node_name} - - {formatBytes(item.total_bytes)} + {devices.map((device) => ( +
+
+
+ {device.platform || device.device_model || device.hwid.slice(0, 12)}
-
-
+
+ {device.device_model && device.platform && ( + {device.device_model} + )} + {device.hwid.slice(0, 8)}... + {device.created_at && ( + + {new Date(device.created_at).toLocaleDateString(locale)} + + )}
- ); - })} + +
+ ))}
) : ( -
-
+
+ {t('admin.users.detail.devices.none')} +
)}
- ) : null} - - {/* Devices */} -
-
- - {t('admin.users.detail.devices.title')} ({devicesTotal}/{deviceLimit}) - -
- - {devices.length > 0 && ( - - )} -
-
- {devicesLoading ? ( -
-
-
- ) : devices.length > 0 ? ( -
- {devices.map((device) => ( -
-
-
- {device.platform || device.device_model || device.hwid.slice(0, 12)} -
-
- {device.device_model && device.platform && ( - {device.device_model} - )} - {device.hwid.slice(0, 8)}... - {device.created_at && ( - {new Date(device.created_at).toLocaleDateString(locale)} - )} -
-
- -
- ))} -
- ) : ( -
- {t('admin.users.detail.devices.none')} -
- )} -
+ )}
)} @@ -2124,6 +2515,26 @@ export default function AdminUserDetail() { {/* Sync Tab */} {activeTab === 'sync' && (
+ {/* Subscription selector for multi-tariff */} + {userSubscriptions.length > 1 && ( +
+
+ {t('admin.users.detail.sync.selectSubscription')} +
+ +
+ )} + {/* Sync status */} {syncStatus && (
+ {syncStatus?.subscription_tariff_name && ( +
+ {syncStatus.subscription_tariff_name} +
+ )}
Remnawave UUID
{syncStatus?.remnawave_uuid || @@ -2645,6 +3061,300 @@ export default function AdminUserDetail() { )}
)} + + {/* Referrals Tab */} + {activeTab === 'referrals' && user && ( +
+ {/* Section 1: Who referred this user */} +
+

+ {t('admin.users.detail.referrals.referredBy')} +

+ + {user.referral.referred_by_id ? ( +
+ + +
+ ) : ( +
+ {showReferrerSearch ? ( +
+
+ setReferrerSearchQuery(e.target.value)} + placeholder={t('admin.users.detail.referrals.searchPlaceholder')} + className="flex-1 rounded-lg border border-dark-600 bg-dark-700 px-3 py-2.5 text-sm text-dark-100 placeholder-dark-500 focus:border-accent-500 focus:outline-none" + autoFocus + /> + +
+ {referrerSearchQuery.length >= 2 && referrerSearchResults.length > 0 && ( +
+ {referrerSearchResults + .filter((u) => u.id !== userId) + .map((u) => ( + + ))} + {referrerSearchResults.filter((u) => u.id !== userId).length === 0 && ( +
+ {t('admin.users.detail.referrals.noUsersFound')} +
+ )} +
+ )} + {referrerSearchQuery.length >= 2 && + !referrerSearchLoading && + referrerSearchResults.length === 0 && ( +
+ {t('admin.users.detail.referrals.noUsersFound')} +
+ )} +
+ ) : ( +
+ + {t('admin.users.detail.referrals.noReferrer')} + + +
+ )} +
+ )} +
+ + {/* Section 2: Referral stats */} +
+
+
+ {t('admin.users.detail.referrals.totalReferrals')} +
+
+ {user.referral.referrals_count} +
+
+
+
+ {t('admin.users.detail.referrals.totalEarnings')} +
+
+ {formatWithCurrency(user.referral.total_earnings_kopeks / 100)} +
+
+
+
+ {t('admin.users.detail.referrals.commission')} +
+
+ {user.referral.commission_percent != null + ? `${user.referral.commission_percent}%` + : t('admin.users.detail.referrals.default')} +
+
+
+
+ {t('admin.users.detail.referrals.referralCode')} +
+
+ {user.referral.referral_code} +
+
+
+ + {/* Section 3: Referrals list */} +
+
+

+ {t('admin.users.detail.referrals.referralsList')} ({referralsTotal}) +

+ {!showAddReferral && ( + + )} +
+ + {/* Add referral search */} + {showAddReferral && ( +
+
+ setAddReferralSearchQuery(e.target.value)} + placeholder={t('admin.users.detail.referrals.searchPlaceholder')} + className="flex-1 rounded-lg border border-dark-600 bg-dark-700 px-3 py-2.5 text-sm text-dark-100 placeholder-dark-500 focus:border-accent-500 focus:outline-none" + autoFocus + /> + +
+ {addReferralSearchQuery.length >= 2 && addReferralSearchResults.length > 0 && ( +
+ {addReferralSearchResults + .filter((u) => u.id !== userId && !referralsList.some((r) => r.id === u.id)) + .map((u) => ( + + ))} + {addReferralSearchResults.filter( + (u) => u.id !== userId && !referralsList.some((r) => r.id === u.id), + ).length === 0 && ( +
+ {t('admin.users.detail.referrals.noUsersFound')} +
+ )} +
+ )} + {addReferralSearchQuery.length >= 2 && + !addReferralSearchLoading && + addReferralSearchResults.length === 0 && ( +
+ {t('admin.users.detail.referrals.noUsersFound')} +
+ )} +
+ )} + + {referralsListLoading ? ( +
+
+
+ ) : referralsList.length === 0 ? ( +
+ {t('admin.users.detail.referrals.noReferrals')} +
+ ) : ( +
+ {referralsList.map((ref) => ( +
+ + +
+ ))} +
+ )} +
+
+ )}
); diff --git a/src/pages/Balance.tsx b/src/pages/Balance.tsx index 83a251e..3879c2f 100644 --- a/src/pages/Balance.tsx +++ b/src/pages/Balance.tsx @@ -80,6 +80,12 @@ export default function Balance() { message: string; amount: number; } | null>(null); + const [promoSelectSubs, setPromoSelectSubs] = useState | null>(null); + const [promoSelectCode, setPromoSelectCode] = useState(null); const [transactionsPage, setTransactionsPage] = useState(1); const [isHistoryOpen, setIsHistoryOpen] = useState(false); @@ -135,27 +141,38 @@ export default function Balance() { } }; - const handlePromocodeActivate = async () => { - if (!promocode.trim()) return; + const handlePromocodeActivate = async (subscriptionId?: number) => { + const code = subscriptionId ? promoSelectCode || '' : promocode.trim(); + if (!code) return; setPromocodeLoading(true); setPromocodeError(null); setPromocodeSuccess(null); try { - const result = await balanceApi.activatePromocode(promocode.trim()); + const result = await balanceApi.activatePromocode(code, subscriptionId); + + if (result.error === 'select_subscription' && result.eligible_subscriptions) { + setPromoSelectSubs(result.eligible_subscriptions); + setPromoSelectCode(result.code || code); + return; + } + if (result.success) { - const bonusAmount = result.balance_after - result.balance_before; + const bonusAmount = (result.balance_after || 0) - (result.balance_before || 0); setPromocodeSuccess({ message: result.bonus_description || t('balance.promocode.success'), amount: bonusAmount, }); setTransactionsPage(1); setPromocode(''); + setPromoSelectSubs(null); + setPromoSelectCode(null); await refetchBalance(); await refreshUser(); queryClient.invalidateQueries({ queryKey: ['transactions'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); } } catch (error: unknown) { const axiosError = error as { response?: { data?: { detail?: string } } }; @@ -170,6 +187,8 @@ export default function Balance() { ? 'already_used_by_user' : 'server_error'; setPromocodeError(t(`balance.promocode.errors.${errorKey}`)); + setPromoSelectSubs(null); + setPromoSelectCode(null); } finally { setPromocodeLoading(false); } @@ -214,7 +233,7 @@ export default function Balance() { disabled={promocodeLoading} /> + ))} + + + )} diff --git a/src/pages/Connection.tsx b/src/pages/Connection.tsx index 24fd911..d22e5f4 100644 --- a/src/pages/Connection.tsx +++ b/src/pages/Connection.tsx @@ -1,5 +1,5 @@ import { useEffect, useCallback, useMemo, useRef } from 'react'; -import { Link, useNavigate } from 'react-router'; +import { Link, useNavigate, useSearchParams } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@tanstack/react-query'; import { openLink as sdkOpenLink } from '@telegram-apps/sdk-react'; @@ -14,6 +14,8 @@ import InstallationGuide from '../components/connection/InstallationGuide'; export default function Connection() { const { t, i18n } = useTranslation(); const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const subId = searchParams.get('sub') ? Number(searchParams.get('sub')) : undefined; const user = useAuthStore((state) => state.user); const isAdmin = useAuthStore((state) => state.isAdmin); const { isTelegramWebApp } = useTelegramSDK(); @@ -27,8 +29,8 @@ export default function Connection() { isLoading, error, } = useQuery({ - queryKey: ['appConfig'], - queryFn: () => subscriptionApi.getAppConfig(), + queryKey: ['appConfig', subId], + queryFn: () => subscriptionApi.getAppConfig(subId), }); const handleGoBack = useCallback(() => { @@ -41,9 +43,10 @@ export default function Connection() { state: { url: appConfig?.subscriptionUrl, hideLink: appConfig?.hideLink ?? false, + subscriptionId: subId, }, }); - }, [navigate, appConfig?.subscriptionUrl, appConfig?.hideLink, isTelegramWebApp]); + }, [navigate, appConfig?.subscriptionUrl, appConfig?.hideLink, isTelegramWebApp, subId]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { diff --git a/src/pages/ConnectionQR.tsx b/src/pages/ConnectionQR.tsx index 5671a2a..f6ae3e4 100644 --- a/src/pages/ConnectionQR.tsx +++ b/src/pages/ConnectionQR.tsx @@ -8,6 +8,7 @@ import { AdminBackButton } from '@/components/admin'; interface ConnectionQRState { url: string; hideLink: boolean; + subscriptionId?: number; } function isValidState(state: unknown): state is ConnectionQRState { @@ -24,12 +25,14 @@ export default function ConnectionQR() { const state = location.state as unknown; const validState = isValidState(state) ? state : null; + const subId = validState?.subscriptionId; + const connectionPath = subId ? `/connection?sub=${subId}` : '/connection'; useEffect(() => { if (!validState) { - navigate('/connection', { replace: true }); + navigate(connectionPath, { replace: true }); } - }, [validState, navigate]); + }, [validState, navigate, connectionPath]); if (!validState) { return null; @@ -38,7 +41,7 @@ export default function ConnectionQR() { return (
- +

{t('subscription.connection.qrTitle')}

diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 184145e..5fa39d9 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useMemo, useRef } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { Link } from 'react-router'; +import { Link, useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../store/auth'; import { useBlockingStore } from '../store/blocking'; @@ -18,6 +18,7 @@ import StatsGrid from '../components/dashboard/StatsGrid'; import { giftApi } from '../api/gift'; import { promoApi } from '../api/promo'; import PendingGiftCard from '../components/dashboard/PendingGiftCard'; +import SubscriptionListCard from '../components/subscription/SubscriptionListCard'; import { API } from '../config/constants'; const ChevronRightIcon = () => ( @@ -28,6 +29,7 @@ const ChevronRightIcon = () => ( export default function Dashboard() { const { t } = useTranslation(); + const navigate = useNavigate(); const user = useAuthStore((state) => state.user); const refreshUser = useAuthStore((state) => state.refreshUser); const queryClient = useQueryClient(); @@ -49,26 +51,35 @@ export default function Dashboard() { refetchOnMount: 'always', }); + // Multi-tariff: check if user has multiple subscriptions + const { data: multiSubData } = useQuery({ + queryKey: ['subscriptions-list'], + queryFn: () => subscriptionApi.getSubscriptions(), + staleTime: 60_000, + }); + const isMultiTariff = multiSubData?.multi_tariff_enabled ?? false; + const { data: subscriptionResponse, isLoading: subLoading } = useQuery({ queryKey: ['subscription'], - queryFn: subscriptionApi.getSubscription, + queryFn: () => subscriptionApi.getSubscription(), retry: false, staleTime: API.BALANCE_STALE_TIME_MS, refetchOnMount: 'always', + enabled: !isMultiTariff, }); const subscription = subscriptionResponse?.subscription ?? null; const { data: trialInfo, isLoading: trialLoading } = useQuery({ queryKey: ['trial-info'], - queryFn: subscriptionApi.getTrialInfo, + queryFn: () => subscriptionApi.getTrialInfo(), enabled: !subscription && !subLoading, }); const { data: devicesData } = useQuery({ queryKey: ['devices'], - queryFn: subscriptionApi.getDevices, - enabled: !!subscription, + queryFn: () => subscriptionApi.getDevices(), + enabled: !!subscription && !isMultiTariff, staleTime: API.BALANCE_STALE_TIME_MS, }); @@ -99,10 +110,11 @@ export default function Dashboard() { }); const activateTrialMutation = useMutation({ - mutationFn: subscriptionApi.activateTrial, + mutationFn: () => subscriptionApi.activateTrial(), onSuccess: () => { setTrialError(null); queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['trial-info'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); @@ -122,20 +134,23 @@ export default function Dashboard() { } | null>(null); const refreshTrafficMutation = useMutation({ - mutationFn: subscriptionApi.refreshTraffic, + mutationFn: () => subscriptionApi.refreshTraffic(subscription?.id), onSuccess: (data) => { setTrafficData({ traffic_used_gb: data.traffic_used_gb, traffic_used_percent: data.traffic_used_percent, is_unlimited: data.is_unlimited, }); - localStorage.setItem('traffic_refresh_ts', Date.now().toString()); + localStorage.setItem( + `traffic_refresh_ts_${subscription?.id ?? 'default'}`, + Date.now().toString(), + ); if (data.rate_limited && data.retry_after_seconds) { setTrafficRefreshCooldown(data.retry_after_seconds); } else { setTrafficRefreshCooldown(30); } - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscription?.id] }); }, onError: (error: { response?: { status?: number; headers?: { get?: (key: string) => string } }; @@ -164,7 +179,7 @@ export default function Dashboard() { if (hasAutoRefreshed.current) return; hasAutoRefreshed.current = true; - const lastRefresh = localStorage.getItem('traffic_refresh_ts'); + const lastRefresh = localStorage.getItem(`traffic_refresh_ts_${subscription?.id ?? 'default'}`); const now = Date.now(); const cacheMs = API.TRAFFIC_CACHE_MS; @@ -266,37 +281,76 @@ export default function Dashboard() { {/* Pending Gift Activations */} {pendingGifts && pendingGifts.length > 0 && } - {/* Subscription Status Card */} - {subLoading ? ( -
-
-
-
-
-
-
-
-
-
+ {/* Multi-tariff: show subscription cards (max 3) */} + {isMultiTariff && multiSubData?.subscriptions && ( +
+
+ + {t('dashboard.subscriptions', 'Подписки')} + + + {t('dashboard.manageAll', 'Управление')} → +
+ {multiSubData.subscriptions.slice(0, 3).map((sub) => ( + navigate(`/subscriptions/${sub.id}`)} + /> + ))} + {multiSubData.subscriptions.length > 3 && ( + + {t('dashboard.showAll', 'Показать все')} ({multiSubData.subscriptions.length}) + + )} + + + {t('subscriptions.buyAnother', 'Купить ещё тариф')} +
- ) : subscription?.is_expired || - subscription?.status === 'disabled' || - subscription?.is_limited ? ( - - ) : subscription ? ( - - ) : null} + )} + + {/* Subscription Status Card — hidden in multi-tariff (managed via /subscriptions) */} + {!isMultiTariff && ( + <> + {subLoading ? ( +
+
+
+
+
+
+
+
+
+
+
+
+ ) : subscription?.is_expired || + subscription?.status === 'disabled' || + subscription?.is_limited ? ( + + ) : subscription ? ( + + ) : null} + + )} {/* Trial Activation */} {hasNoSubscription && !trialLoading && trialInfo?.is_available && ( diff --git a/src/pages/DeepLinkRedirect.tsx b/src/pages/DeepLinkRedirect.tsx index 60c0828..d036eeb 100644 --- a/src/pages/DeepLinkRedirect.tsx +++ b/src/pages/DeepLinkRedirect.tsx @@ -311,7 +311,7 @@ export default function DeepLinkRedirect() { {/* Back to cabinet */}

{t('deepLink.errorTitle')}

{t('deepLink.errorDesc')}

-
diff --git a/src/pages/GiftResult.tsx b/src/pages/GiftResult.tsx index d647223..beb24a7 100644 --- a/src/pages/GiftResult.tsx +++ b/src/pages/GiftResult.tsx @@ -55,8 +55,10 @@ function CodeOnlySuccessState({ const shortCode = purchaseToken.slice(0, 12); const giftCode = `GIFT-${shortCode}`; const botUsername = import.meta.env.VITE_TELEGRAM_BOT_USERNAME as string | undefined; - const botLink = botUsername ? `https://t.me/${botUsername}?start=GIFT_${shortCode}` : null; - const cabinetLink = `${window.location.origin}/gift?tab=activate&code=${shortCode}`; + // Encode underscores as %5F so Telegram auto-link detection doesn't strip them + const safeCode = shortCode.replace(/_/g, '%5F'); + const botLink = botUsername ? `https://t.me/${botUsername}?start=GIFT%5F${safeCode}` : null; + const cabinetLink = `${window.location.origin}/gift?tab=activate&code=${safeCode}`; const fullMessage = [ t('gift.shareText', 'I have a gift for you! Activate it here:'), diff --git a/src/pages/GiftSubscription.tsx b/src/pages/GiftSubscription.tsx index 892ad8d..781e36d 100644 --- a/src/pages/GiftSubscription.tsx +++ b/src/pages/GiftSubscription.tsx @@ -1040,8 +1040,10 @@ function SentGiftCard({ gift }: { gift: SentGift }) { const buildShareMessage = useCallback(() => { const botUsername = import.meta.env.VITE_TELEGRAM_BOT_USERNAME as string | undefined; - const botLink = botUsername ? `https://t.me/${botUsername}?start=GIFT_${shortCode}` : null; - const cabinetLink = `${window.location.origin}/gift?tab=activate&code=${shortCode}`; + // Encode underscores as %5F so Telegram auto-link detection doesn't strip them + const safeCode = shortCode.replace(/_/g, '%5F'); + const botLink = botUsername ? `https://t.me/${botUsername}?start=GIFT%5F${safeCode}` : null; + const cabinetLink = `${window.location.origin}/gift?tab=activate&code=${safeCode}`; return [ t('gift.shareText'), '', @@ -1304,7 +1306,9 @@ export default function GiftSubscription() { // URL params: ?tab=activate&code=TOKEN for auto-activation const urlTab = searchParams.get('tab') as TabId | null; - const urlCode = searchParams.get('code'); + const rawCode = searchParams.get('code'); + // Strip GIFT- or GIFT_ prefix if user pasted the full display code + const urlCode = rawCode?.replace(/^GIFT[-_]/i, '') ?? rawCode; const [activeTab, setActiveTab] = useState( urlTab === 'activate' || urlTab === 'myGifts' ? urlTab : 'buy', ); diff --git a/src/pages/NewsArticle.tsx b/src/pages/NewsArticle.tsx index 85de576..979ec06 100644 --- a/src/pages/NewsArticle.tsx +++ b/src/pages/NewsArticle.tsx @@ -408,7 +408,7 @@ export default function NewsArticlePage() { {article.title} diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index 197ae83..eb09234 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -1,6 +1,7 @@ import { useState, useRef, useEffect } from 'react'; import { Link, useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; +import { usePlatform } from '@/platform'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { motion, AnimatePresence } from 'framer-motion'; import { useAuthStore } from '../store/auth'; @@ -147,7 +148,12 @@ export default function Profile() { const registerEmailMutation = useMutation({ mutationFn: ({ email, password }: { email: string; password: string }) => authApi.registerEmail(email, password), - onSuccess: async () => { + onSuccess: async (response) => { + // Backend returns merge_required when email belongs to another user + if (response.merge_required && response.merge_token) { + navigate(`/merge/${response.merge_token}`, { replace: true }); + return; + } setSuccess(t('profile.emailSent')); setError(null); setEmail(''); @@ -250,14 +256,16 @@ export default function Profile() { return () => clearInterval(timer); }, [verificationResendCooldown]); - // Auto-focus inputs on step change + // Auto-focus inputs on step change (skip on Telegram — keyboard hides bottom nav) + const { platform: profilePlatform } = usePlatform(); useEffect(() => { + if (profilePlatform === 'telegram') return; const timer = setTimeout(() => { if (changeEmailStep === 'email') newEmailInputRef.current?.focus(); else if (changeEmailStep === 'code') codeInputRef.current?.focus(); }, 100); return () => clearTimeout(timer); - }, [changeEmailStep]); + }, [changeEmailStep, profilePlatform]); // Auto-close success after 3s useEffect(() => { diff --git a/src/pages/QuickPurchase.tsx b/src/pages/QuickPurchase.tsx index f981d13..8dc3877 100644 --- a/src/pages/QuickPurchase.tsx +++ b/src/pages/QuickPurchase.tsx @@ -927,6 +927,7 @@ export default function QuickPurchase() { contact_type: detectContactType(contactValue), contact_value: contactValue.trim(), payment_method: paymentMethod, + language: i18n.language, is_gift: isGift, }; diff --git a/src/pages/RenewSubscription.tsx b/src/pages/RenewSubscription.tsx new file mode 100644 index 0000000..8dded97 --- /dev/null +++ b/src/pages/RenewSubscription.tsx @@ -0,0 +1,234 @@ +import { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; +import { Navigate, useNavigate, useParams } from 'react-router'; +import { subscriptionApi } from '../api/subscription'; +import { useTheme } from '../hooks/useTheme'; +import { getGlassColors } from '../utils/glassTheme'; +import { useCurrency } from '../hooks/useCurrency'; +import { useHaptic } from '../platform'; +import InsufficientBalancePrompt from '../components/InsufficientBalancePrompt'; +import { WebBackButton } from '../components/WebBackButton'; + +export default function RenewSubscription() { + const { subscriptionId } = useParams<{ subscriptionId: string }>(); + const subId = subscriptionId ? Number(subscriptionId) : undefined; + + const { t } = useTranslation(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const { isDark } = useTheme(); + const g = getGlassColors(isDark); + const { formatAmount, currencySymbol } = useCurrency(); + const { impact } = useHaptic(); + + const [selectedPeriod, setSelectedPeriod] = useState(null); + const [error, setError] = useState(null); + + // Load subscription detail for tariff name + const { data: subscriptionResponse } = useQuery({ + queryKey: ['subscription', subId], + queryFn: () => subscriptionApi.getSubscription(subId), + enabled: !!subId, + staleTime: 30_000, + }); + const subscription = subscriptionResponse?.subscription ?? null; + + // Load renewal options + const { data: options, isLoading } = useQuery({ + queryKey: ['renewal-options', subId], + queryFn: () => subscriptionApi.getRenewalOptions(subId), + enabled: !!subId, + staleTime: 0, + refetchOnMount: 'always', + }); + + // Load balance + const { data: purchaseOptions } = useQuery({ + queryKey: ['purchase-options', subId], + queryFn: () => subscriptionApi.getPurchaseOptions(subId), + staleTime: 0, + }); + const balanceKopeks = purchaseOptions?.balance_kopeks ?? 0; + + const renewMutation = useMutation({ + mutationFn: (periodDays: number) => subscriptionApi.renewSubscription(periodDays, subId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['subscription', subId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); + queryClient.invalidateQueries({ queryKey: ['renewal-options', subId] }); + queryClient.invalidateQueries({ queryKey: ['balance'] }); + navigate(`/subscriptions/${subId}`, { replace: true }); + }, + onError: (err: unknown) => { + const detail = + err && typeof err === 'object' && 'response' in err + ? ((err as { response?: { data?: { detail?: unknown } } }).response?.data?.detail ?? null) + : null; + + if (detail && typeof detail === 'object' && 'code' in (detail as Record)) { + const typed = detail as { code: string; missing_amount?: number }; + if (typed.code === 'insufficient_funds' && typed.missing_amount) { + setError(`insufficient:${typed.missing_amount}`); + return; + } + } + setError(typeof detail === 'string' ? detail : t('common.error')); + }, + }); + + const handleRenew = (periodDays: number) => { + impact('medium'); + setError(null); + renewMutation.mutate(periodDays); + }; + + if (!subId) { + return ; + } + + if (isLoading) { + return ( +
+
+
+ ); + } + + const insufficientMatch = error?.match(/^insufficient:(\d+)$/); + const missingAmount = insufficientMatch ? Number(insufficientMatch[1]) : null; + + return ( +
+ {/* Title */} +
+ +
+

+ {t('subscription.extend', 'Продлить подписку')} +

+ {subscription?.tariff_name && ( +

+ {subscription.tariff_name} +

+ )} +
+
+ + {/* Balance */} +
+ + {t('common.balance', 'Баланс')} + + + {formatAmount(balanceKopeks / 100)} {currencySymbol} + +
+ + {/* Period options */} + {!options || options.length === 0 ? ( +
+

+ {t('subscription.noRenewalOptions', 'Нет доступных вариантов продления')} +

+
+ ) : ( +
+ {options.map((option) => { + const isSelected = selectedPeriod === option.period_days; + const canAfford = balanceKopeks >= option.price_kopeks; + const months = Math.max(1, Math.round(option.period_days / 30)); + const perMonth = option.price_kopeks / months; + + return ( + + ); + })} +
+ )} + + {/* Insufficient balance prompt */} + {missingAmount && } + + {/* Error */} + {error && !missingAmount && ( +
{error}
+ )} + + {/* Renew button */} + {selectedPeriod && ( + + )} +
+ ); +} diff --git a/src/pages/Subscription.tsx b/src/pages/Subscription.tsx index e55104e..d1e70d0 100644 --- a/src/pages/Subscription.tsx +++ b/src/pages/Subscription.tsx @@ -1,8 +1,11 @@ import { useState, useEffect, useRef, useCallback, memo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router'; +import { Navigate, useNavigate, useParams } from 'react-router'; import { subscriptionApi } from '../api/subscription'; +import { WebBackButton } from '../components/WebBackButton'; +import { useDestructiveConfirm } from '../platform/hooks/useNativeDialog'; +import { usePlatform } from '../platform'; import TrafficProgressBar from '../components/dashboard/TrafficProgressBar'; import { HoverBorderGradient } from '../components/ui/hover-border-gradient'; import { useTrafficZone } from '../hooks/useTrafficZone'; @@ -168,10 +171,16 @@ export default function Subscription() { const queryClient = useQueryClient(); const { formatAmount, currencySymbol } = useCurrency(); const navigate = useNavigate(); + const { subscriptionId: subIdParam } = useParams<{ subscriptionId?: string }>(); + const subscriptionId = subIdParam ? parseInt(subIdParam, 10) : undefined; const { isDark } = useTheme(); const g = getGlassColors(isDark); const haptic = useHaptic(); const [copied, setCopied] = useState(false); + const [showDeleteSheet, setShowDeleteSheet] = useState(false); + const [deleteLoading, setDeleteLoading] = useState(false); + const { platform } = usePlatform(); + const destructiveConfirm = useDestructiveConfirm(); // Helper to format price from kopeks const formatPrice = (kopeks: number) => `${formatAmount(kopeks / 100)} ${currencySymbol}`; @@ -194,11 +203,19 @@ export default function Subscription() { is_unlimited: boolean; } | null>(null); + // Detect multi-tariff mode from cached subscriptions-list + const { data: multiSubData } = useQuery({ + queryKey: ['subscriptions-list'], + queryFn: () => subscriptionApi.getSubscriptions(), + staleTime: 60_000, + }); + const isMultiTariff = multiSubData?.multi_tariff_enabled ?? false; + const { data: subscriptionResponse, isLoading } = useQuery({ - queryKey: ['subscription'], - queryFn: subscriptionApi.getSubscription, + queryKey: ['subscription', subscriptionId], + queryFn: () => subscriptionApi.getSubscription(subscriptionId), retry: false, - staleTime: 0, // Always refetch to get latest data + staleTime: 0, refetchOnMount: 'always', }); @@ -211,8 +228,8 @@ export default function Subscription() { // Purchase options (needed for balance_kopeks in device/traffic/server management) const { data: purchaseOptions } = useQuery({ - queryKey: ['purchase-options'], - queryFn: subscriptionApi.getPurchaseOptions, + queryKey: ['purchase-options', subscriptionId], + queryFn: () => subscriptionApi.getPurchaseOptions(subscriptionId), staleTime: 0, refetchOnMount: 'always', }); @@ -220,40 +237,43 @@ export default function Subscription() { const isTariffsMode = purchaseOptions?.sales_mode === 'tariffs'; const autopayMutation = useMutation({ - mutationFn: (enabled: boolean) => subscriptionApi.updateAutopay(enabled), + mutationFn: (enabled: boolean) => + subscriptionApi.updateAutopay(enabled, undefined, subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); }, }); // Devices query const { data: devicesData, isLoading: devicesLoading } = useQuery({ - queryKey: ['devices'], - queryFn: subscriptionApi.getDevices, + queryKey: ['devices', subscriptionId], + queryFn: () => subscriptionApi.getDevices(subscriptionId), enabled: !!subscription, }); // Delete device mutation const deleteDeviceMutation = useMutation({ - mutationFn: (hwid: string) => subscriptionApi.deleteDevice(hwid), + mutationFn: (hwid: string) => subscriptionApi.deleteDevice(hwid, subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['devices'] }); + queryClient.invalidateQueries({ queryKey: ['devices', subscriptionId] }); }, }); // Delete all devices mutation const deleteAllDevicesMutation = useMutation({ - mutationFn: () => subscriptionApi.deleteAllDevices(), + mutationFn: () => subscriptionApi.deleteAllDevices(subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['devices'] }); + queryClient.invalidateQueries({ queryKey: ['devices', subscriptionId] }); }, }); // Pause subscription mutation const pauseMutation = useMutation({ - mutationFn: () => subscriptionApi.togglePause(), + mutationFn: () => subscriptionApi.togglePause(subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); }, }); @@ -269,18 +289,20 @@ export default function Subscription() { // Device price query const { data: devicePriceData } = useQuery({ - queryKey: ['device-price', devicesToAdd], - queryFn: () => subscriptionApi.getDevicePrice(devicesToAdd), + queryKey: ['device-price', devicesToAdd, subscriptionId], + queryFn: () => subscriptionApi.getDevicePrice(devicesToAdd, subscriptionId), enabled: showDeviceTopup && !!subscription, }); // Device purchase mutation const devicePurchaseMutation = useMutation({ - mutationFn: () => subscriptionApi.purchaseDevices(devicesToAdd), + mutationFn: () => subscriptionApi.purchaseDevices(devicesToAdd, subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['subscription'] }); - queryClient.invalidateQueries({ queryKey: ['devices'] }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); + queryClient.invalidateQueries({ queryKey: ['devices', subscriptionId] }); queryClient.invalidateQueries({ queryKey: ['device-price'] }); + queryClient.invalidateQueries({ queryKey: ['balance'] }); setShowDeviceTopup(false); setDevicesToAdd(1); }, @@ -288,8 +310,8 @@ export default function Subscription() { // Device reduction info query const { data: deviceReductionInfo } = useQuery({ - queryKey: ['device-reduction-info'], - queryFn: subscriptionApi.getDeviceReductionInfo, + queryKey: ['device-reduction-info', subscriptionId], + queryFn: () => subscriptionApi.getDeviceReductionInfo(subscriptionId), enabled: showDeviceReduction && !!subscription, }); @@ -307,27 +329,31 @@ export default function Subscription() { // Device reduction mutation const deviceReductionMutation = useMutation({ - mutationFn: () => subscriptionApi.reduceDevices(targetDeviceLimit), + mutationFn: () => subscriptionApi.reduceDevices(targetDeviceLimit, subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['subscription'] }); - queryClient.invalidateQueries({ queryKey: ['devices'] }); - queryClient.invalidateQueries({ queryKey: ['device-reduction-info'] }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); + queryClient.invalidateQueries({ queryKey: ['devices', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['device-reduction-info', subscriptionId] }); setShowDeviceReduction(false); }, }); // Traffic packages query const { data: trafficPackages } = useQuery({ - queryKey: ['traffic-packages'], - queryFn: subscriptionApi.getTrafficPackages, + queryKey: ['traffic-packages', subscriptionId], + queryFn: () => subscriptionApi.getTrafficPackages(subscriptionId), enabled: showTrafficTopup && !!subscription, }); // Traffic purchase mutation const trafficPurchaseMutation = useMutation({ - mutationFn: (gb: number) => subscriptionApi.purchaseTraffic(gb), + mutationFn: (gb: number) => subscriptionApi.purchaseTraffic(gb, subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); + queryClient.invalidateQueries({ queryKey: ['balance'] }); + queryClient.invalidateQueries({ queryKey: ['traffic-packages', subscriptionId] }); setShowTrafficTopup(false); setSelectedTrafficPackage(null); }, @@ -335,8 +361,8 @@ export default function Subscription() { // Countries/servers query const { data: countriesData, isLoading: countriesLoading } = useQuery({ - queryKey: ['countries'], - queryFn: subscriptionApi.getCountries, + queryKey: ['countries', subscriptionId], + queryFn: () => subscriptionApi.getCountries(subscriptionId), enabled: showServerManagement && !!subscription && !subscription.is_trial, }); @@ -350,30 +376,34 @@ export default function Subscription() { // Countries update mutation const updateCountriesMutation = useMutation({ - mutationFn: (countries: string[]) => subscriptionApi.updateCountries(countries), + mutationFn: (countries: string[]) => subscriptionApi.updateCountries(countries, subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['subscription'] }); - queryClient.invalidateQueries({ queryKey: ['countries'] }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); + queryClient.invalidateQueries({ queryKey: ['countries', subscriptionId] }); setShowServerManagement(false); }, }); // Traffic refresh mutation const refreshTrafficMutation = useMutation({ - mutationFn: subscriptionApi.refreshTraffic, + mutationFn: () => subscriptionApi.refreshTraffic(subscriptionId), onSuccess: (data) => { setTrafficData({ traffic_used_gb: data.traffic_used_gb, traffic_used_percent: data.traffic_used_percent, is_unlimited: data.is_unlimited, }); - localStorage.setItem('traffic_refresh_ts', Date.now().toString()); + localStorage.setItem( + `traffic_refresh_ts_${subscriptionId ?? 'default'}`, + Date.now().toString(), + ); if (data.rate_limited && data.retry_after_seconds) { setTrafficRefreshCooldown(data.retry_after_seconds); } else { setTrafficRefreshCooldown(30); } - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); }, onError: (error: { response?: { status?: number; headers?: { get?: (key: string) => string } }; @@ -403,7 +433,7 @@ export default function Subscription() { if (hasAutoRefreshed.current) return; hasAutoRefreshed.current = true; - const lastRefresh = localStorage.getItem('traffic_refresh_ts'); + const lastRefresh = localStorage.getItem(`traffic_refresh_ts_${subscriptionId ?? 'default'}`); const now = Date.now(); const cacheMs = 30 * 1000; @@ -417,7 +447,7 @@ export default function Subscription() { } refreshTrafficMutation.mutate(); - }, [subscription, refreshTrafficMutation]); + }, [subscription, refreshTrafficMutation, subscriptionId]); const copyUrl = () => { if (subscription?.subscription_url) { @@ -427,6 +457,11 @@ export default function Subscription() { } }; + // In multi-tariff mode without a specific subscription ID, redirect to list + if (isMultiTariff && !subscriptionId && !isLoading) { + return ; + } + if (isLoading) { return (
@@ -435,9 +470,37 @@ export default function Subscription() { ); } + if (!subscription && subscriptionId) { + return ( +
+
😕
+

+ {t('subscription.notFound', 'Подписка не найдена')} +

+

+ {t('subscription.notFoundDesc', 'Возможно, подписка была удалена или не существует')} +

+ +
+ ); + } + return (
-

{t('subscription.title')}

+ {/* Page title */} +
+ +

+ {isMultiTariff && subscription?.tariff_name + ? subscription.tariff_name + : t('subscription.title')} +

+
{/* Current Subscription */} {subscription ? ( @@ -736,7 +799,7 @@ export default function Subscription() { haptic.notification('error'); return; } - navigate('/connection'); + navigate(subscriptionId ? `/connection?sub=${subscriptionId}` : '/connection'); }} className={`mb-5 flex w-full items-center gap-3.5 rounded-[14px] p-3.5 text-left transition-shadow duration-300${isAtDeviceLimit ? 'cursor-not-allowed opacity-50' : ''}`} style={{ fontFamily: 'inherit' }} @@ -1210,7 +1273,100 @@ export default function Subscription() { )} {/* Purchase / Renewal CTA */} - + + + {/* Delete expired subscription */} + {isMultiTariff && subscription && !subscription.is_active && !subscription.is_trial && ( +
+ {!showDeleteSheet ? ( + + ) : ( +
+
+ {t('subscription.deleteTitle', 'Удалить подписку?')} +
+
+ {t( + 'subscription.deleteWarning', + 'Подписка будет удалена безвозвратно. Все данные, устройства и настройки будут потеряны. Это действие нельзя отменить.', + )} +
+
+ + +
+
+ )} +
+ )} {/* Additional Options (Buy Devices) */} {subscription && @@ -1381,7 +1537,7 @@ export default function Subscription() { } compact onBeforeTopUp={async () => { - await subscriptionApi.saveDevicesCart(devicesToAdd); + await subscriptionApi.saveDevicesCart(devicesToAdd, subscriptionId); }} /> )} @@ -1721,7 +1877,10 @@ export default function Subscription() { compact className="mb-3" onBeforeTopUp={async () => { - await subscriptionApi.saveTrafficCart(selectedTrafficPackage); + await subscriptionApi.saveTrafficCart( + selectedTrafficPackage, + subscriptionId, + ); }} /> )} diff --git a/src/pages/SubscriptionPurchase.tsx b/src/pages/SubscriptionPurchase.tsx index 829a9fe..13d29f9 100644 --- a/src/pages/SubscriptionPurchase.tsx +++ b/src/pages/SubscriptionPurchase.tsx @@ -1,10 +1,11 @@ import { useState, useEffect, useMemo, useRef, useCallback } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router'; +import { useNavigate, useSearchParams } from 'react-router'; import { AxiosError } from 'axios'; import { subscriptionApi } from '../api/subscription'; import { promoApi } from '../api/promo'; +import { WebBackButton } from '../components/WebBackButton'; import { getGlassColors } from '../utils/glassTheme'; import { useTheme } from '../hooks/useTheme'; import type { @@ -28,6 +29,10 @@ export default function SubscriptionPurchase() { const { t } = useTranslation(); const queryClient = useQueryClient(); const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const subscriptionId = searchParams.get('subscriptionId') + ? parseInt(searchParams.get('subscriptionId')!, 10) + : undefined; const { formatAmount, currencySymbol } = useCurrency(); const { isDark } = useTheme(); const g = getGlassColors(isDark); @@ -36,8 +41,8 @@ export default function SubscriptionPurchase() { // Subscription query (shares cache with /subscription page) const { data: subscriptionResponse, isLoading } = useQuery({ - queryKey: ['subscription'], - queryFn: subscriptionApi.getSubscription, + queryKey: ['subscription', subscriptionId], + queryFn: () => subscriptionApi.getSubscription(subscriptionId), retry: false, staleTime: 0, refetchOnMount: 'always', @@ -51,8 +56,8 @@ export default function SubscriptionPurchase() { isError: optionsError, refetch: refetchOptions, } = useQuery({ - queryKey: ['purchase-options'], - queryFn: subscriptionApi.getPurchaseOptions, + queryKey: ['purchase-options', subscriptionId], + queryFn: () => subscriptionApi.getPurchaseOptions(subscriptionId), staleTime: 0, refetchOnMount: 'always', }); @@ -70,6 +75,14 @@ export default function SubscriptionPurchase() { const tariffs = isTariffsMode && purchaseOptions && 'tariffs' in purchaseOptions ? purchaseOptions.tariffs : []; + // Multi-tariff: check via subscriptions list query + const { data: multiSubData } = useQuery({ + queryKey: ['subscriptions-list'], + queryFn: () => subscriptionApi.getSubscriptions(), + staleTime: 60_000, + }); + const isMultiTariff = multiSubData?.multi_tariff_enabled ?? false; + // Helper to apply promo discount const applyPromoDiscount = ( priceKopeks: number, @@ -217,36 +230,38 @@ export default function SubscriptionPurchase() { // Preview query (classic) const { data: preview, isLoading: previewLoading } = useQuery({ queryKey: ['purchase-preview', currentSelection], - queryFn: () => subscriptionApi.previewPurchase(currentSelection), + queryFn: () => subscriptionApi.previewPurchase(currentSelection, subscriptionId), enabled: !!selectedPeriod && showPurchaseForm && currentStep === 'confirm', }); // Classic purchase mutation const purchaseMutation = useMutation({ - mutationFn: () => subscriptionApi.submitPurchase(currentSelection), + mutationFn: () => subscriptionApi.submitPurchase(currentSelection, subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['subscription'] }); - queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); - navigate('/subscription', { replace: true }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['purchase-options', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['balance'] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); + navigate('/subscriptions', { replace: true }); }, }); // Switch preview query const { data: switchPreview, isLoading: switchPreviewLoading } = useQuery({ queryKey: ['tariff-switch-preview', switchTariffId], - queryFn: () => subscriptionApi.previewTariffSwitch(switchTariffId!), + queryFn: () => subscriptionApi.previewTariffSwitch(switchTariffId!, subscriptionId), enabled: !!switchTariffId, }); // Tariff switch mutation const switchTariffMutation = useMutation({ - mutationFn: (tariffId: number) => subscriptionApi.switchTariff(tariffId), + mutationFn: (tariffId: number) => subscriptionApi.switchTariff(tariffId, subscriptionId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['subscription'] }); - queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); + queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['purchase-options', subscriptionId] }); setSwitchTariffId(null); - navigate('/subscription', { replace: true }); + navigate('/subscriptions', { replace: true }); }, onError: (error: unknown) => { if (error instanceof AxiosError) { @@ -263,7 +278,7 @@ export default function SubscriptionPurchase() { setSelectedTariff(targetTariff); setSelectedTariffPeriod(targetTariff.periods[0] || null); setShowTariffPurchase(true); - queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); + queryClient.invalidateQueries({ queryKey: ['purchase-options', subscriptionId] }); } } } @@ -291,7 +306,8 @@ export default function SubscriptionPurchase() { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['subscription'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); - navigate('/subscription', { replace: true }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); + navigate('/subscriptions', { replace: true }); }, }); @@ -370,34 +386,7 @@ export default function SubscriptionPurchase() { if (optionsError || (!purchaseOptions && !optionsLoading)) { return (
-
- -

- {t('subscription.extend')} -

-
+

{t('subscription.extend')}

- {/* Header with back link */} + {/* Header */}
- +

- {subscription?.is_daily && !subscription?.is_trial - ? t('subscription.switchTariff.title') - : subscription && !subscription.is_trial - ? t('subscription.extend') - : t('subscription.getSubscription')} + {isMultiTariff && !subscriptionId + ? t('subscription.newTariff', 'Новый тариф') + : !isMultiTariff && subscription?.is_daily && !subscription?.is_trial + ? t('subscription.switchTariff.title') + : subscription && !subscription.is_trial + ? t('subscription.extend') + : t('subscription.getSubscription')}

@@ -748,9 +719,37 @@ export default function SubscriptionPurchase() { )} {/* Tariff Grid */} + {isMultiTariff && + purchaseOptions && + 'all_tariffs_purchased' in purchaseOptions && + purchaseOptions.all_tariffs_purchased && ( +
+
+

+ {t('subscription.allTariffsPurchased', 'Все тарифы подключены')} +

+

+ {t( + 'subscription.allTariffsPurchasedDesc', + 'Вы уже приобрели все доступные тарифы. Продлить подписку можно на странице тарифа.', + )} +

+ +
+ )}
{[...tariffs] .filter((tariff) => { + // In multi-tariff mode: hide already purchased tariffs + if (isMultiTariff && tariff.is_purchased) return false; if (subscription?.is_trial && tariff.name.toLowerCase().includes('trial')) { return false; } @@ -772,6 +771,7 @@ export default function SubscriptionPurchase() { 'subscription_is_expired' in purchaseOptions && purchaseOptions.subscription_is_expired === true; const canSwitch = + !isMultiTariff && subscription && subscription.tariff_id && !isCurrentTariff && diff --git a/src/pages/Subscriptions.tsx b/src/pages/Subscriptions.tsx new file mode 100644 index 0000000..1b2e600 --- /dev/null +++ b/src/pages/Subscriptions.tsx @@ -0,0 +1,137 @@ +import { useQuery } from '@tanstack/react-query'; +import { Navigate, useNavigate } from 'react-router'; +import { useTranslation } from 'react-i18next'; +import { subscriptionApi } from '../api/subscription'; +import { useTheme } from '../hooks/useTheme'; +import { getGlassColors } from '../utils/glassTheme'; +import SubscriptionListCard from '../components/subscription/SubscriptionListCard'; + +function EmptyState({ onBuy }: { onBuy: () => void }) { + const { t } = useTranslation(); + const { isDark } = useTheme(); + const g = getGlassColors(isDark); + + return ( +
+
+ + + +
+

+ {t('subscriptions.empty', 'Нет подписок')} +

+

+ {t('subscriptions.emptyDesc', 'У вас пока нет активных подписок')} +

+ +
+ ); +} + +export default function Subscriptions() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { isDark } = useTheme(); + const g = getGlassColors(isDark); + + const { data, isLoading } = useQuery({ + queryKey: ['subscriptions-list'], + queryFn: () => subscriptionApi.getSubscriptions(), + staleTime: 30_000, + refetchOnMount: 'always', + }); + + const subscriptions = data?.subscriptions ?? []; + const isMultiTariff = data?.multi_tariff_enabled ?? false; + + // Single-tariff mode with one subscription: skip list, go directly to detail + if (data && !isMultiTariff && subscriptions.length === 1) { + return ; + } + + return ( +
+ {/* Header */} +
+

+ {t('subscriptions.title', 'Мои подписки')} +

+ {!isLoading && subscriptions.length > 0 && ( + + )} +
+ + {/* Loading */} + {isLoading && ( +
+ {[1, 2].map((i) => ( +
+ ))} +
+ )} + + {/* Empty state */} + {!isLoading && subscriptions.length === 0 && ( + navigate('/subscription/purchase')} /> + )} + + {/* Subscription grid */} + {subscriptions.length > 0 && ( +
+ {subscriptions.map((sub) => ( + navigate(`/subscriptions/${sub.id}`)} + /> + ))} +
+ )} +
+ ); +} diff --git a/src/pages/TopUpAmount.tsx b/src/pages/TopUpAmount.tsx index 4f3ac73..fac7ef8 100644 --- a/src/pages/TopUpAmount.tsx +++ b/src/pages/TopUpAmount.tsx @@ -88,7 +88,7 @@ export default function TopUpAmount() { const queryClient = useQueryClient(); const { formatAmount, currencySymbol, convertAmount, convertToRub, targetCurrency } = useCurrency(); - const { openInvoice, openTelegramLink, openLink } = usePlatform(); + const { openInvoice, openTelegramLink, openLink, platform } = usePlatform(); const haptic = useHaptic(); const inputRef = useRef(null); @@ -228,15 +228,16 @@ export default function TopUpAmount() { }, }); - // Auto-focus input + // Auto-focus input (only on desktop — mobile keyboard hides bottom nav) useEffect(() => { + if (platform === 'telegram') return; const timer = setTimeout(() => { if (inputRef.current) { inputRef.current.focus(); } }, 100); return () => clearTimeout(timer); - }, []); + }, [platform]); if (!method) { return ( @@ -399,7 +400,6 @@ export default function TopUpAmount() { placeholder="0" className="h-14 w-full bg-transparent px-4 pr-12 text-xl font-bold text-dark-100 placeholder:text-dark-600 focus:outline-none" autoComplete="off" - autoFocus /> {currencySymbol} diff --git a/src/pages/TopUpResult.tsx b/src/pages/TopUpResult.tsx index 783a308..63c6c7f 100644 --- a/src/pages/TopUpResult.tsx +++ b/src/pages/TopUpResult.tsx @@ -305,7 +305,10 @@ export default function TopUpResult() { clearTopUpPendingInfo(); queryClient.invalidateQueries({ queryKey: ['balance'] }); queryClient.invalidateQueries({ queryKey: ['transactions'] }); - queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ + predicate: (query) => Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); refreshUser(); } else if (resolvedFailed) { diff --git a/src/pages/Wheel.tsx b/src/pages/Wheel.tsx index 737b05b..eb4b8e5 100644 --- a/src/pages/Wheel.tsx +++ b/src/pages/Wheel.tsx @@ -107,6 +107,7 @@ export default function Wheel() { const [isPayingStars, setIsPayingStars] = useState(false); const [historyExpanded, setHistoryExpanded] = useState(false); const [showStarsConfirm, setShowStarsConfirm] = useState(false); + const [selectedSubscriptionId, setSelectedSubscriptionId] = useState(null); const paymentTypeInitialized = useRef(false); const { @@ -136,6 +137,11 @@ export default function Wheel() { } else if (daysEnabled) { setPaymentType('subscription_days'); } + + // Auto-select subscription if only one eligible + if (config.eligible_subscriptions?.length === 1) { + setSelectedSubscriptionId(config.eligible_subscriptions[0].id); + } }, [config]); // Function to poll for new spin result after Stars payment @@ -368,7 +374,7 @@ export default function Wheel() { }; const spinMutation = useMutation({ - mutationFn: () => wheelApi.spin(paymentType), + mutationFn: () => wheelApi.spin(paymentType, selectedSubscriptionId ?? undefined), onSuccess: (result) => { if (result.success) { setTargetRotation(result.rotation_degrees); @@ -506,11 +512,18 @@ export default function Wheel() { // Stars via Telegram invoice don't require ruble balance, so only check daily limit const dailyLimitReached = config.daily_limit > 0 && config.user_spins_today >= config.daily_limit; const noSubscription = !config.has_subscription; + const needsSubscriptionPick = + paymentType === 'subscription_days' && + config.eligible_subscriptions && + config.eligible_subscriptions.length > 1 && + !selectedSubscriptionId; + const spinDisabled = isSpinning || isPayingStars || dailyLimitReached || noSubscription || + needsSubscriptionPick || (paymentType === 'telegram_stars' ? !starsEnabled : !config.can_spin); return ( @@ -582,6 +595,38 @@ export default function Wheel() {
)} + {/* Subscription selector for days payment in multi-tariff */} + {paymentType === 'subscription_days' && + config.eligible_subscriptions && + config.eligible_subscriptions.length > 1 && ( +
+

+ {t('wheel.selectSubscription', 'Выберите подписку')} +

+
+ {config.eligible_subscriptions.map((sub) => ( + + ))} +
+
+ )} + {/* Stars confirmation panel */} {showStarsConfirm && !isSpinning && !isPayingStars ? (
@@ -645,6 +690,14 @@ export default function Wheel() {

{t('wheel.errors.dailyLimitReached')}

)} + {/* Subscription selection required hint */} + {!isSpinning && needsSubscriptionPick && ( +
+

+ {t('wheel.errors.selectSubscription', 'Выберите подписку для списания дней')} +

+
+ )} {/* Inline Result Card */} {spinResult && !isSpinning && ( diff --git a/src/providers/WebSocketContext.ts b/src/providers/WebSocketContext.ts index edee8f2..6c4cbb1 100644 --- a/src/providers/WebSocketContext.ts +++ b/src/providers/WebSocketContext.ts @@ -16,6 +16,7 @@ export interface WSMessage { new_balance_rubles?: number; description?: string; // Subscription events + subscription_id?: number; expires_at?: string; new_expires_at?: string; tariff_name?: string; diff --git a/src/styles/globals.css b/src/styles/globals.css index e9e5364..ba512f9 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1642,3 +1642,82 @@ input[type='checkbox']:hover:not(:checked) { .light .sheet-handle { @apply bg-champagne-400; } + +/* ── Admin Panel: Floating Orbs Background ── */ +@keyframes adminOrbFloat { + 0% { + transform: translate(0, 0) scale(1); + } + 33% { + transform: translate(30px, -40px) scale(1.05); + } + 66% { + transform: translate(-20px, 30px) scale(0.95); + } + 100% { + transform: translate(10px, -20px) scale(1.02); + } +} + +@keyframes adminPulse { + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.4; + transform: scale(0.85); + } +} + +@keyframes adminCardEnter { + from { + opacity: 0; + transform: translateY(20px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes adminItemEnter { + from { + opacity: 0; + transform: translateX(-8px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.admin-orb { + position: absolute; + border-radius: 50%; + filter: blur(120px); + pointer-events: none; + animation: adminOrbFloat 20s ease-in-out infinite alternate; +} + +.light .admin-orb { + filter: blur(140px); + opacity: 0.15 !important; +} + +/* Mobile: reduce orb count and blur for performance */ +@media (max-width: 1023px) { + .admin-orb { + filter: blur(80px); + } + .admin-orb:nth-child(n + 4) { + display: none; + } +} + +@media (prefers-reduced-motion: reduce) { + .admin-orb { + animation: none; + } +} diff --git a/src/types/index.ts b/src/types/index.ts index ad61a5e..edb6d87 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -110,6 +110,31 @@ export interface SubscriptionStatusResponse { subscription: Subscription | null; } +// Multi-tariff subscription list item (from GET /cabinet/subscriptions) +export interface SubscriptionListItem { + id: number; + status: string; + tariff_id: number | null; + tariff_name: string | null; + traffic_limit_gb: number; + traffic_used_gb: number; + device_limit: number; + end_date: string | null; + subscription_url: string | null; + subscription_crypto_link: string | null; + is_trial: boolean; + is_daily?: boolean; + is_daily_paused?: boolean; + autopay_enabled: boolean; + connected_squads: string[] | null; +} + +// Response from GET /cabinet/subscriptions (multi-tariff) +export interface SubscriptionsListResponse { + subscriptions: SubscriptionListItem[]; + multi_tariff_enabled: boolean; +} + // Device types export interface Device { hwid: string; @@ -320,6 +345,8 @@ export interface Tariff { custom_days_discount_percent?: number; // Traffic reset traffic_reset_mode?: string; + // Multi-tariff: already purchased by user + is_purchased?: boolean; } export interface TariffsPurchaseOptions { @@ -332,6 +359,8 @@ export interface TariffsPurchaseOptions { subscription_status?: string; subscription_is_expired?: boolean; has_subscription?: boolean; + // Multi-tariff: all available tariffs already purchased + all_tariffs_purchased?: boolean; } export interface ClassicPurchaseOptions { diff --git a/src/utils/format.ts b/src/utils/format.ts index ca851a1..f084109 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -1,3 +1,12 @@ +export function formatUptime(seconds: number): string { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + if (days > 0) return `${days}d ${hours}h`; + if (hours > 0) return `${hours}h ${minutes}m`; + return `${minutes}m`; +} + export function formatPrice(kopeks: number): string { const rubles = kopeks / 100; return new Intl.NumberFormat('ru-RU', { diff --git a/tailwind.config.js b/tailwind.config.js index 6977e02..155b39b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -282,5 +282,9 @@ export default { }, }, }, - plugins: [], + plugins: [ + function ({ addVariant }) { + addVariant('light', '.light &'); + }, + ], };