From da9da0971f765983665833b8ce9b06918a6f5d53 Mon Sep 17 00:00:00 2001 From: nikita Date: Fri, 12 Jun 2026 00:54:50 +0300 Subject: [PATCH 01/15] routing --- package-lock.json | 86 +++++++++---- package.json | 3 +- src/App.tsx | 122 ------------------- src/app/App.tsx | 11 ++ src/app/providers/helper/protected.route.tsx | 17 +++ src/app/providers/routing.tsx | 32 +++++ src/main.tsx | 16 +-- src/pages/home.page.tsx | 7 ++ src/pages/secondary.page.tsx | 7 ++ tsconfig.app.json | 5 + vite.config.ts | 12 +- 11 files changed, 158 insertions(+), 160 deletions(-) delete mode 100644 src/App.tsx create mode 100644 src/app/App.tsx create mode 100644 src/app/providers/helper/protected.route.tsx create mode 100644 src/app/providers/routing.tsx create mode 100644 src/pages/home.page.tsx create mode 100644 src/pages/secondary.page.tsx diff --git a/package-lock.json b/package-lock.json index 323cb31..048b6de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.0", "dependencies": { "react": "^19.2.6", - "react-dom": "^19.2.6" + "react-dom": "^19.2.6", + "react-router-dom": "^7.17.0" }, "devDependencies": { "@eslint/js": "^10.0.1", @@ -267,31 +268,6 @@ "node": ">=6.9.0" } }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -1314,6 +1290,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2371,6 +2360,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2378,6 +2368,44 @@ "react": "^19.2.7" } }, + "node_modules/react-router": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz", + "integrity": "sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.17.0.tgz", + "integrity": "sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==", + "license": "MIT", + "dependencies": { + "react-router": "7.17.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/rolldown": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", @@ -2428,6 +2456,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index 44698a5..14a24c3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ }, "dependencies": { "react": "^19.2.6", - "react-dom": "^19.2.6" + "react-dom": "^19.2.6", + "react-router-dom": "^7.17.0" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index a66b5ef..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from './assets/vite.svg' -import heroImg from './assets/hero.png' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
-
- - React logo - Vite logo -
-
-

Get started

-

- Edit src/App.tsx and save to test HMR -

-
- -
- -
- -
-
- -

Documentation

-

Your questions, answered

- -
-
- -

Connect with us

-

Join the Vite community

- -
-
- -
-
- - ) -} - -export default App diff --git a/src/app/App.tsx b/src/app/App.tsx new file mode 100644 index 0000000..5b397a3 --- /dev/null +++ b/src/app/App.tsx @@ -0,0 +1,11 @@ +import { Routing } from "./providers/routing"; + +function App() { + return ( + <> + + + ); +} + +export default App; diff --git a/src/app/providers/helper/protected.route.tsx b/src/app/providers/helper/protected.route.tsx new file mode 100644 index 0000000..67fa1b3 --- /dev/null +++ b/src/app/providers/helper/protected.route.tsx @@ -0,0 +1,17 @@ +import { Navigate } from "react-router-dom"; + +interface ProtectedRouteProps { + children: React.ReactNode; + fallbackPath?: string; +} + +export const ProtectedRoute: React.FC = ({ + children, + fallbackPath = "/", +}) => { + if (false) { + return ; + } + + return <>{children}; +}; diff --git a/src/app/providers/routing.tsx b/src/app/providers/routing.tsx new file mode 100644 index 0000000..d515bba --- /dev/null +++ b/src/app/providers/routing.tsx @@ -0,0 +1,32 @@ +import { Suspense } from "react"; +import { HomePage } from "@/pages/home.page"; +import { SecondaryPage } from "@/pages/secondary.page"; +import { ProtectedRoute } from "./helper/protected.route"; +import { Routes as ReactRoutes, Route } from "react-router-dom"; + +export const Routing = () => { + return ( + + Загрузка... + + } + > + + } /> + + + + + + } + /> + + + + ); +}; diff --git a/src/main.tsx b/src/main.tsx index bef5202..348d8fc 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,10 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./app/App.tsx"; +import { BrowserRouter } from "react-router-dom"; -createRoot(document.getElementById('root')!).render( - +createRoot(document.getElementById("root")!).render( + - , -) + , +); diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx new file mode 100644 index 0000000..5a94c07 --- /dev/null +++ b/src/pages/home.page.tsx @@ -0,0 +1,7 @@ +export const HomePage = () => { + return( +
+ Домашняя +
+ ) +} \ No newline at end of file diff --git a/src/pages/secondary.page.tsx b/src/pages/secondary.page.tsx new file mode 100644 index 0000000..72b729d --- /dev/null +++ b/src/pages/secondary.page.tsx @@ -0,0 +1,7 @@ +export const SecondaryPage = () => { + return( +
+ Вторичная +
+ ) +} \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 7f42e5f..a90b99c 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -7,6 +7,11 @@ "types": ["vite/client"], "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, diff --git a/vite.config.ts b/vite.config.ts index 8b0f57b..4874cd8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,13 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import path from "path"; +import react from "@vitejs/plugin-react"; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], -}) + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, +}); -- 2.52.0 From df52c0e8b86d446c7b62b9d1a772f728fb859bbf Mon Sep 17 00:00:00 2001 From: nikita Date: Fri, 12 Jun 2026 00:58:32 +0300 Subject: [PATCH 02/15] setup taillwind --- package-lock.json | 407 +++++++++++++++++++++++++++----- package.json | 4 +- src/App.css | 184 --------------- src/app/App.tsx | 1 + src/index.css | 111 --------- src/main.tsx | 3 +- src/pages/home.page.tsx | 8 +- src/shared/styles/index.css | 14 ++ src/shared/styles/normalize.css | 365 ++++++++++++++++++++++++++++ src/shared/styles/root.css | 307 ++++++++++++++++++++++++ src/shared/styles/themes.css | 267 +++++++++++++++++++++ vite.config.ts | 3 +- 12 files changed, 1314 insertions(+), 360 deletions(-) delete mode 100644 src/App.css delete mode 100644 src/index.css create mode 100644 src/shared/styles/index.css create mode 100644 src/shared/styles/normalize.css create mode 100644 src/shared/styles/root.css create mode 100644 src/shared/styles/themes.css diff --git a/package-lock.json b/package-lock.json index 048b6de..c87c59f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "app", "version": "0.0.0", "dependencies": { + "@tailwindcss/vite": "^4.3.0", "react": "^19.2.6", "react-dom": "^19.2.6", - "react-router-dom": "^7.17.0" + "react-router-dom": "^7.17.0", + "tailwindcss": "^4.3.0" }, "devDependencies": { "@eslint/js": "^10.0.1", @@ -269,10 +271,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "license": "MIT", "optional": true, "dependencies": { @@ -477,7 +478,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -488,7 +488,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -499,7 +498,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -509,14 +507,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -527,7 +523,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -546,7 +541,6 @@ "version": "0.133.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -559,7 +553,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -576,7 +569,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -593,7 +585,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -610,7 +601,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -627,7 +617,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -644,7 +633,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -661,7 +649,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -678,7 +665,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -695,7 +681,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -712,7 +697,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -729,7 +713,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -746,7 +729,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -763,7 +745,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -775,6 +756,37 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", @@ -782,7 +794,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -799,7 +810,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -813,14 +823,269 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, "license": "MIT" }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -852,7 +1117,7 @@ "version": "24.13.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -1354,7 +1619,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -1367,6 +1631,19 @@ "dev": true, "license": "ISC" }, + "node_modules/enhanced-resolve": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz", + "integrity": "sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1598,7 +1875,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -1667,7 +1943,6 @@ "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, @@ -1714,6 +1989,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -1781,6 +2062,15 @@ "dev": true, "license": "ISC" }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1863,7 +2153,6 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -1896,7 +2185,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1917,7 +2205,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1938,7 +2225,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1959,7 +2245,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1980,7 +2265,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2001,7 +2285,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2022,7 +2305,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2043,7 +2325,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2064,7 +2345,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2085,7 +2365,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2106,7 +2385,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2146,6 +2424,15 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -2173,7 +2460,6 @@ "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, "funding": [ { "type": "github", @@ -2279,14 +2565,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, "license": "MIT", "peer": true, "engines": { @@ -2300,7 +2584,6 @@ "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2410,7 +2693,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", - "dev": true, "license": "MIT", "dependencies": { "@oxc-project/types": "=0.133.0", @@ -2489,17 +2771,34 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -2529,7 +2828,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD", "optional": true }, @@ -2589,7 +2887,7 @@ "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -2637,7 +2935,6 @@ "version": "8.0.16", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { diff --git a/package.json b/package.json index 14a24c3..088728a 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.3.0", "react": "^19.2.6", "react-dom": "^19.2.6", - "react-router-dom": "^7.17.0" + "react-router-dom": "^7.17.0", + "tailwindcss": "^4.3.0" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/src/App.css b/src/App.css deleted file mode 100644 index f90339d..0000000 --- a/src/App.css +++ /dev/null @@ -1,184 +0,0 @@ -.counter { - font-size: 16px; - padding: 5px 10px; - border-radius: 5px; - color: var(--accent); - background: var(--accent-bg); - border: 2px solid transparent; - transition: border-color 0.3s; - margin-bottom: 24px; - - &:hover { - border-color: var(--accent-border); - } - &:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } -} - -.hero { - position: relative; - - .base, - .framework, - .vite { - inset-inline: 0; - margin: 0 auto; - } - - .base { - width: 170px; - position: relative; - z-index: 0; - } - - .framework, - .vite { - position: absolute; - } - - .framework { - z-index: 1; - top: 34px; - height: 28px; - transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) - scale(1.4); - } - - .vite { - z-index: 0; - top: 107px; - height: 26px; - width: auto; - transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) - scale(0.8); - } -} - -#center { - display: flex; - flex-direction: column; - gap: 25px; - place-content: center; - place-items: center; - flex-grow: 1; - - @media (max-width: 1024px) { - padding: 32px 20px 24px; - gap: 18px; - } -} - -#next-steps { - display: flex; - border-top: 1px solid var(--border); - text-align: left; - - & > div { - flex: 1 1 0; - padding: 32px; - @media (max-width: 1024px) { - padding: 24px 20px; - } - } - - .icon { - margin-bottom: 16px; - width: 22px; - height: 22px; - } - - @media (max-width: 1024px) { - flex-direction: column; - text-align: center; - } -} - -#docs { - border-right: 1px solid var(--border); - - @media (max-width: 1024px) { - border-right: none; - border-bottom: 1px solid var(--border); - } -} - -#next-steps ul { - list-style: none; - padding: 0; - display: flex; - gap: 8px; - margin: 32px 0 0; - - .logo { - height: 18px; - } - - a { - color: var(--text-h); - font-size: 16px; - border-radius: 6px; - background: var(--social-bg); - display: flex; - padding: 6px 12px; - align-items: center; - gap: 8px; - text-decoration: none; - transition: box-shadow 0.3s; - - &:hover { - box-shadow: var(--shadow); - } - .button-icon { - height: 18px; - width: 18px; - } - } - - @media (max-width: 1024px) { - margin-top: 20px; - flex-wrap: wrap; - justify-content: center; - - li { - flex: 1 1 calc(50% - 8px); - } - - a { - width: 100%; - justify-content: center; - box-sizing: border-box; - } - } -} - -#spacer { - height: 88px; - border-top: 1px solid var(--border); - @media (max-width: 1024px) { - height: 48px; - } -} - -.ticks { - position: relative; - width: 100%; - - &::before, - &::after { - content: ''; - position: absolute; - top: -4.5px; - border: 5px solid transparent; - } - - &::before { - left: 0; - border-left-color: var(--border); - } - &::after { - right: 0; - border-right-color: var(--border); - } -} diff --git a/src/app/App.tsx b/src/app/App.tsx index 5b397a3..9a228b3 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,4 +1,5 @@ import { Routing } from "./providers/routing"; +import "@/shared/styles/index.css"; function App() { return ( diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 5fb3313..0000000 --- a/src/index.css +++ /dev/null @@ -1,111 +0,0 @@ -:root { - --text: #6b6375; - --text-h: #08060d; - --bg: #fff; - --border: #e5e4e7; - --code-bg: #f4f3ec; - --accent: #aa3bff; - --accent-bg: rgba(170, 59, 255, 0.1); - --accent-border: rgba(170, 59, 255, 0.5); - --social-bg: rgba(244, 243, 236, 0.5); - --shadow: - rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; - - --sans: system-ui, 'Segoe UI', Roboto, sans-serif; - --heading: system-ui, 'Segoe UI', Roboto, sans-serif; - --mono: ui-monospace, Consolas, monospace; - - font: 18px/145% var(--sans); - letter-spacing: 0.18px; - color-scheme: light dark; - color: var(--text); - background: var(--bg); - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - @media (max-width: 1024px) { - font-size: 16px; - } -} - -@media (prefers-color-scheme: dark) { - :root { - --text: #9ca3af; - --text-h: #f3f4f6; - --bg: #16171d; - --border: #2e303a; - --code-bg: #1f2028; - --accent: #c084fc; - --accent-bg: rgba(192, 132, 252, 0.15); - --accent-border: rgba(192, 132, 252, 0.5); - --social-bg: rgba(47, 48, 58, 0.5); - --shadow: - rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; - } - - #social .button-icon { - filter: invert(1) brightness(2); - } -} - -#root { - width: 1126px; - max-width: 100%; - margin: 0 auto; - text-align: center; - border-inline: 1px solid var(--border); - min-height: 100svh; - display: flex; - flex-direction: column; - box-sizing: border-box; -} - -body { - margin: 0; -} - -h1, -h2 { - font-family: var(--heading); - font-weight: 500; - color: var(--text-h); -} - -h1 { - font-size: 56px; - letter-spacing: -1.68px; - margin: 32px 0; - @media (max-width: 1024px) { - font-size: 36px; - margin: 20px 0; - } -} -h2 { - font-size: 24px; - line-height: 118%; - letter-spacing: -0.24px; - margin: 0 0 8px; - @media (max-width: 1024px) { - font-size: 20px; - } -} -p { - margin: 0; -} - -code, -.counter { - font-family: var(--mono); - display: inline-flex; - border-radius: 4px; - color: var(--text-h); -} - -code { - font-size: 15px; - line-height: 135%; - padding: 4px 8px; - background: var(--code-bg); -} diff --git a/src/main.tsx b/src/main.tsx index 348d8fc..3d8b726 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,6 @@ -import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import App from "./app/App.tsx"; import { BrowserRouter } from "react-router-dom"; +import App from "./app/App.tsx"; createRoot(document.getElementById("root")!).render( diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 5a94c07..58fd7c1 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -1,7 +1,3 @@ export const HomePage = () => { - return( -
- Домашняя -
- ) -} \ No newline at end of file + return
Домашняя
; +}; diff --git a/src/shared/styles/index.css b/src/shared/styles/index.css new file mode 100644 index 0000000..580b131 --- /dev/null +++ b/src/shared/styles/index.css @@ -0,0 +1,14 @@ +@import "tailwindcss"; +@import "./normalize.css"; +@import "./root.css"; +@import "./themes.css"; + +/* Hide scrollbar but keep functionality */ +.scrollbar-hide { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.scrollbar-hide::-webkit-scrollbar { + display: none; /* Chrome, Safari and Opera */ +} diff --git a/src/shared/styles/normalize.css b/src/shared/styles/normalize.css new file mode 100644 index 0000000..e7c7aac --- /dev/null +++ b/src/shared/styles/normalize.css @@ -0,0 +1,365 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + overflow-x: hidden; + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; + /* 1 */ + height: 0; + /* 1 */ + overflow: visible; + /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +/* a { + background-color: transparent; +} */ + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; + /* 1 */ + text-decoration: underline; + /* 2 */ + text-decoration: underline dotted; + /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { + /* 1 */ + overflow: visible; + outline: none; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { + /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; + /* 1 */ + color: inherit; + /* 2 */ + display: table; + /* 1 */ + max-width: 100%; + /* 1 */ + padding: 0; + /* 3 */ + white-space: normal; + /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; + /* 1 */ + padding: 0; + /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/src/shared/styles/root.css b/src/shared/styles/root.css new file mode 100644 index 0000000..e0312e1 --- /dev/null +++ b/src/shared/styles/root.css @@ -0,0 +1,307 @@ +/* Дополнительные стили для PrimeReact с вашей темой */ +.p-tabmenu .p-tabmenuitem .p-menuitem-link { + color: var(--text-secondary); + transition: all 0.2s ease; +} + +.p-tabmenu .p-tabmenuitem .p-menuitem-link:not(.p-disabled):hover { + color: var(--text-primary); + background-color: var(--bg-tertiary); +} + +.p-tabmenu .p-tabmenuitem.p-highlight .p-menuitem-link { + color: var(--accent-primary); + border-color: var(--accent-primary); +} + +.p-menubar { + background-color: var(--bg-secondary); + border: none; + border-radius: 0; +} + +.p-menubar .p-menuitem-link { + color: var(--text-secondary); +} + +.p-menubar .p-menuitem-link:hover { + background-color: var(--bg-tertiary); + color: var(--text-primary); +} + +.p-button.p-button-text { + color: var(--text-secondary); +} + +.p-button.p-button-text:hover { + color: var(--text-primary); + background-color: var(--bg-tertiary); +} + +/* ==================== Стили для скроллов ==================== */ + +/* WebKit браузеры (Chrome, Safari, Edge, Opera) */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: var(--border-secondary); + border-radius: 4px; + transition: background 0.2s ease; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +::-webkit-scrollbar-corner { + background: var(--bg-tertiary); +} + +/* Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: var(--border-secondary) var(--bg-tertiary); +} + +/* Для элементов с прокруткой (кастомные классы) */ +.custom-scrollbar { + scrollbar-width: thin; + scrollbar-color: var(--border-secondary) var(--bg-tertiary); +} + +.custom-scrollbar::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.custom-scrollbar::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 4px; +} + +.custom-scrollbar::-webkit-scrollbar-thumb { + background: var(--border-secondary); + border-radius: 4px; + transition: background 0.2s ease; +} + +.custom-scrollbar::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Для горизонтальных скроллов */ +.horizontal-scrollbar { + overflow-x: auto; +} + +.horizontal-scrollbar::-webkit-scrollbar { + height: 6px; +} + +/* Для очень тонких скроллов (например, в таблицах) */ +.thin-scrollbar::-webkit-scrollbar { + width: 4px; + height: 4px; +} + +.thin-scrollbar::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 2px; +} + +.thin-scrollbar::-webkit-scrollbar-thumb { + background: var(--border-secondary); + border-radius: 2px; +} + +.thin-scrollbar::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Для темных тем - дополнительная стилизация */ +[data-theme="dark"] ::-webkit-scrollbar-track, +[data-theme="nightowl"] ::-webkit-scrollbar-track, +[data-theme="sunset"] ::-webkit-scrollbar-track, +[data-theme="forest"] ::-webkit-scrollbar-track, +[data-theme="ocean"] ::-webkit-scrollbar-track, +[data-theme="coffee"] ::-webkit-scrollbar-track, +[data-theme="midnight"] ::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); +} + +[data-theme="dark"] ::-webkit-scrollbar-thumb, +[data-theme="nightowl"] ::-webkit-scrollbar-thumb, +[data-theme="sunset"] ::-webkit-scrollbar-thumb, +[data-theme="forest"] ::-webkit-scrollbar-thumb, +[data-theme="ocean"] ::-webkit-scrollbar-thumb, +[data-theme="coffee"] ::-webkit-scrollbar-thumb, +[data-theme="midnight"] ::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); +} + +[data-theme="dark"] ::-webkit-scrollbar-thumb:hover, +[data-theme="nightowl"] ::-webkit-scrollbar-thumb:hover, +[data-theme="sunset"] ::-webkit-scrollbar-thumb:hover, +[data-theme="forest"] ::-webkit-scrollbar-thumb:hover, +[data-theme="ocean"] ::-webkit-scrollbar-thumb:hover, +[data-theme="coffee"] ::-webkit-scrollbar-thumb:hover, +[data-theme="midnight"] ::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Для светлых тем - более контрастные скроллы */ +[data-theme="light"] ::-webkit-scrollbar-track { + background: #f1f5f9; +} + +[data-theme="light"] ::-webkit-scrollbar-thumb { + background: #cbd5e1; +} + +[data-theme="light"] ::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Для лавандовой темы */ +[data-theme="lavender"] ::-webkit-scrollbar-track { + background: #e9d5ff; +} + +[data-theme="lavender"] ::-webkit-scrollbar-thumb { + background: #c084fc; +} + +[data-theme="lavender"] ::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Для розовой темы */ +[data-theme="rose"] ::-webkit-scrollbar-track { + background: #fecdd3; +} + +[data-theme="rose"] ::-webkit-scrollbar-thumb { + background: #fb7185; +} + +[data-theme="rose"] ::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Стили для скролла в текстовых полях и textarea */ +textarea::-webkit-scrollbar { + width: 6px; +} + +textarea::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 3px; +} + +textarea::-webkit-scrollbar-thumb { + background: var(--border-secondary); + border-radius: 3px; +} + +textarea::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Стили для скролла в выпадающих списках PrimeReact */ +.p-dropdown-panel .p-dropdown-items-wrapper::-webkit-scrollbar { + width: 6px; +} + +.p-dropdown-panel .p-dropdown-items-wrapper::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 3px; +} + +.p-dropdown-panel .p-dropdown-items-wrapper::-webkit-scrollbar-thumb { + background: var(--border-secondary); + border-radius: 3px; +} + +.p-dropdown-panel .p-dropdown-items-wrapper::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Стили для скролла в таблицах */ +.p-datatable-wrapper::-webkit-scrollbar { + height: 8px; + width: 8px; +} + +.p-datatable-wrapper::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 4px; +} + +.p-datatable-wrapper::-webkit-scrollbar-thumb { + background: var(--border-secondary); + border-radius: 4px; +} + +.p-datatable-wrapper::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Стили для скролла в модальных окнах */ +.p-dialog .p-dialog-content::-webkit-scrollbar { + width: 6px; +} + +.p-dialog .p-dialog-content::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 3px; +} + +.p-dialog .p-dialog-content::-webkit-scrollbar-thumb { + background: var(--border-secondary); + border-radius: 3px; +} + +.p-dialog .p-dialog-content::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +/* Стили для скролла в меню */ +.p-menu .p-menu-list::-webkit-scrollbar { + width: 6px; +} + +.p-menu .p-menu-list::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 3px; +} + +.p-menu .p-menu-list::-webkit-scrollbar-thumb { + background: var(--border-secondary); + border-radius: 3px; +} + +.p-menu .p-menu-list::-webkit-scrollbar-thumb:hover { + background: var(--accent-primary); +} + +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.animate-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} diff --git a/src/shared/styles/themes.css b/src/shared/styles/themes.css new file mode 100644 index 0000000..465ad9a --- /dev/null +++ b/src/shared/styles/themes.css @@ -0,0 +1,267 @@ +@import "tailwindcss"; + +/* Tailwind dark mode через data-theme атрибут */ +@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *)); + +/* Кастомные утилиты */ +@layer utilities { + .transition-theme { + transition-property: background-color, border-color, color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; + } +} + +/* =========================== + БАЗОВЫЕ ТЕМЫ (Light/Dark) + =========================== */ + +/* Светлая тема (по умолчанию) */ +:root, +[data-theme="light"] { + --bg-primary: #ffffff; + --bg-secondary: #f8fafc; + --bg-tertiary: #f1f5f9; + --text-primary: #0f172a; + --text-secondary: #475569; + --text-muted: #94a3b8; + --border: #e2e8f0; + --border-focus: #94a3b8; + --card-bg: #ffffff; + --input-bg: #ffffff; + --header-bg: #ffffff; + --button-primary: #0f172a; + --button-primary-text: #ffffff; + --button-primary-hover: #1e293b; + --button-danger: #ef4444; + --button-danger-text: #ffffff; + --button-danger-hover: #dc2626; + --error-bg: #fef2f2; + --error-border: #fecaca; + --error-text: #dc2626; + --shadow-color: rgba(0, 0, 0, 0.1); + --accent: #6366f1; + --accent-hover: #4f46e5; + --link: #0f172a; + --link-hover: #475569; +} + +/* Темная тема */ +[data-theme="dark"] { + --bg-primary: #0f172a; + --bg-secondary: #1e293b; + --bg-tertiary: #334155; + --text-primary: #f8fafc; + --text-secondary: #cbd5e1; + --text-muted: #64748b; + --border: #334155; + --border-focus: #64748b; + --card-bg: #1e293b; + --input-bg: #1e293b; + --header-bg: #0f172a; + --button-primary: #f8fafc; + --button-primary-text: #0f172a; + --button-primary-hover: #e2e8f0; + --button-danger: #ef4444; + --button-danger-text: #ffffff; + --button-danger-hover: #f87171; + --error-bg: rgba(239, 68, 68, 0.1); + --error-border: rgba(239, 68, 68, 0.3); + --error-text: #fca5a5; + --shadow-color: rgba(0, 0, 0, 0.3); + --accent: #818cf8; + --accent-hover: #a5b4fc; + --link: #f8fafc; + --link-hover: #cbd5e1; +} + +/* =========================== + ЦВЕТНЫЕ ТЕМЫ + =========================== */ + +/* Night Owl */ +[data-theme="nightowl"] { + --bg-primary: #011627; + --bg-secondary: #011e3c; + --bg-tertiary: #0d2f4f; + --text-primary: #d6deeb; + --text-secondary: #8892b0; + --text-muted: #4a5568; + --border: #1d3b53; + --border-focus: #7dd3fc; + --card-bg: #011e3c; + --input-bg: #011e3c; + --header-bg: #011627; + --button-primary: #7dd3fc; + --button-primary-text: #011627; + --button-primary-hover: #bae6fd; + --button-danger: #f87171; + --button-danger-text: #ffffff; + --button-danger-hover: #fca5a5; + --error-bg: rgba(248, 113, 113, 0.1); + --error-border: rgba(248, 113, 113, 0.3); + --error-text: #fca5a5; + --shadow-color: rgba(0, 0, 0, 0.4); + --accent: #7dd3fc; + --accent-hover: #bae6fd; + --link: #7dd3fc; + --link-hover: #bae6fd; +} + +/* Sunset */ +[data-theme="sunset"] { + --bg-primary: #1c1917; + --bg-secondary: #292524; + --bg-tertiary: #44403c; + --text-primary: #fafaf9; + --text-secondary: #d6d3d1; + --text-muted: #78716c; + --border: #57534e; + --border-focus: #f97316; + --card-bg: #292524; + --input-bg: #292524; + --header-bg: #1c1917; + --button-primary: #f97316; + --button-primary-text: #1c1917; + --button-primary-hover: #fb923c; + --button-danger: #ef4444; + --button-danger-text: #ffffff; + --button-danger-hover: #f87171; + --error-bg: rgba(239, 68, 68, 0.1); + --error-border: rgba(239, 68, 68, 0.3); + --error-text: #fca5a5; + --shadow-color: rgba(0, 0, 0, 0.4); + --accent: #f97316; + --accent-hover: #fb923c; + --link: #f97316; + --link-hover: #fb923c; +} + +/* Forest */ +[data-theme="forest"] { + --bg-primary: #052e16; + --bg-secondary: #14532d; + --bg-tertiary: #166534; + --text-primary: #f0fdf4; + --text-secondary: #bbf7d0; + --text-muted: #4ade80; + --border: #166534; + --border-focus: #22c55e; + --card-bg: #14532d; + --input-bg: #14532d; + --header-bg: #052e16; + --button-primary: #22c55e; + --button-primary-text: #052e16; + --button-primary-hover: #4ade80; + --button-danger: #ef4444; + --button-danger-text: #ffffff; + --button-danger-hover: #f87171; + --error-bg: rgba(239, 68, 68, 0.1); + --error-border: rgba(239, 68, 68, 0.3); + --error-text: #fca5a5; + --shadow-color: rgba(0, 0, 0, 0.4); + --accent: #22c55e; + --accent-hover: #4ade80; + --link: #22c55e; + --link-hover: #4ade80; +} + +/* Ocean */ +[data-theme="ocean"] { + --bg-primary: #164e63; + --bg-secondary: #0e7490; + --bg-tertiary: #0891b2; + --text-primary: #f0fdfd; + --text-secondary: #a5f3fc; + --text-muted: #22d3ee; + --border: #0891b2; + --border-focus: #06b6d4; + --card-bg: #0e7490; + --input-bg: #0e7490; + --header-bg: #164e63; + --button-primary: #06b6d4; + --button-primary-text: #164e63; + --button-primary-hover: #22d3ee; + --button-danger: #ef4444; + --button-danger-text: #ffffff; + --button-danger-hover: #f87171; + --error-bg: rgba(239, 68, 68, 0.1); + --error-border: rgba(239, 68, 68, 0.3); + --error-text: #fca5a5; + --shadow-color: rgba(0, 0, 0, 0.4); + --accent: #06b6d4; + --accent-hover: #22d3ee; + --link: #06b6d4; + --link-hover: #22d3ee; +} + +/* Lavender */ +[data-theme="lavender"] { + --bg-primary: #faf5ff; + --bg-secondary: #f3e8ff; + --bg-tertiary: #e9d5ff; + --text-primary: #581c87; + --text-secondary: #7e22ce; + --text-muted: #a855f7; + --border: #e9d5ff; + --border-focus: #a855f7; + --card-bg: #f3e8ff; + --input-bg: #f3e8ff; + --header-bg: #faf5ff; + --button-primary: #a855f7; + --button-primary-text: #faf5ff; + --button-primary-hover: #c084fc; + --button-danger: #ef4444; + --button-danger-text: #ffffff; + --button-danger-hover: #f87171; + --error-bg: rgba(239, 68, 68, 0.1); + --error-border: rgba(239, 68, 68, 0.3); + --error-text: #dc2626; + --shadow-color: rgba(88, 28, 135, 0.1); + --accent: #a855f7; + --accent-hover: #c084fc; + --link: #7e22ce; + --link-hover: #a855f7; +} + +/* Coffee */ +[data-theme="coffee"] { + --bg-primary: #292524; + --bg-secondary: #44403c; + --bg-tertiary: #57534e; + --text-primary: #f5f5f4; + --text-secondary: #d6d3d1; + --text-muted: #a8a29e; + --border: #57534e; + --border-focus: #d97706; + --card-bg: #44403c; + --input-bg: #44403c; + --header-bg: #292524; + --button-primary: #d97706; + --button-primary-text: #292524; + --button-primary-hover: #fbbf24; + --button-danger: #ef4444; + --button-danger-text: #ffffff; + --button-danger-hover: #f87171; + --error-bg: rgba(239, 68, 68, 0.1); + --error-border: rgba(239, 68, 68, 0.3); + --error-text: #fca5a5; + --shadow-color: rgba(0, 0, 0, 0.4); + --accent: #d97706; + --accent-hover: #fbbf24; + --link: #d97706; + --link-hover: #fbbf24; +} + +/* =========================== + БАЗОВЫЕ СТИЛИ + =========================== */ + +body { + background-color: var(--bg-primary); + color: var(--text-primary); + transition: + background-color 0.3s ease, + color 0.3s ease, + border-color 0.3s ease; +} diff --git a/vite.config.ts b/vite.config.ts index 4874cd8..5d1e6ad 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,10 +1,11 @@ import { defineConfig } from "vite"; import path from "path"; import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), tailwindcss()], resolve: { alias: { "@": path.resolve(__dirname, "src"), -- 2.52.0 From 8005ba7111bd47ff218d3c00eb406074a16a1509 Mon Sep 17 00:00:00 2001 From: nikita Date: Fri, 12 Jun 2026 01:01:47 +0300 Subject: [PATCH 03/15] axios & api' --- package-lock.json | 311 ++++++++++++++++++++++++++++++- package.json | 1 + src/shared/api/api.service.ts | 37 ++++ src/shared/api/axios.instance.ts | 104 +++++++++++ src/shared/api/hooks/use.api.ts | 32 ++++ src/shared/api/index.ts | 2 + 6 files changed, 485 insertions(+), 2 deletions(-) create mode 100644 src/shared/api/api.service.ts create mode 100644 src/shared/api/axios.instance.ts create mode 100644 src/shared/api/hooks/use.api.ts create mode 100644 src/shared/api/index.ts diff --git a/package-lock.json b/package-lock.json index c87c59f..1e76240 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@tailwindcss/vite": "^4.3.0", + "axios": "^1.17.0", "react": "^19.2.6", "react-dom": "^19.2.6", "react-router-dom": "^7.17.0", @@ -1439,6 +1440,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -1456,6 +1469,24 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", + "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1527,6 +1558,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001799", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", @@ -1548,6 +1592,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1594,7 +1650,6 @@ "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" @@ -1615,6 +1670,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1624,6 +1688,20 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.371", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.371.tgz", @@ -1644,6 +1722,51 @@ "node": ">=10.13.0" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1939,6 +2062,42 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1953,6 +2112,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1963,6 +2131,43 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1989,12 +2194,63 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -2012,6 +2268,19 @@ "hermes-estree": "0.25.1" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2433,6 +2702,36 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -2453,7 +2752,6 @@ "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/nanoid": { @@ -2618,6 +2916,15 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 088728a..f78cb78 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@tailwindcss/vite": "^4.3.0", + "axios": "^1.17.0", "react": "^19.2.6", "react-dom": "^19.2.6", "react-router-dom": "^7.17.0", diff --git a/src/shared/api/api.service.ts b/src/shared/api/api.service.ts new file mode 100644 index 0000000..112a5bf --- /dev/null +++ b/src/shared/api/api.service.ts @@ -0,0 +1,37 @@ +import { apiClient } from "./axios.instance"; +import type { AxiosResponse } from "axios"; + +export interface ApiResponse { + data: T; + message: string; + success: boolean; +} + +class ApiService { + async get(url: string, config?: any): Promise { + const response: AxiosResponse = await apiClient.get(url, config); + return response.data; + } + + async post(url: string, data?: D, config?: any): Promise { + const response: AxiosResponse = await apiClient.post(url, data, config); + return response.data; + } + + async put(url: string, data?: D, config?: any): Promise { + const response: AxiosResponse = await apiClient.put(url, data, config); + return response.data; + } + + async patch(url: string, data?: D, config?: any): Promise { + const response: AxiosResponse = await apiClient.patch(url, data, config); + return response.data; + } + + async delete(url: string, config?: any): Promise { + const response: AxiosResponse = await apiClient.delete(url, config); + return response.data; + } +} + +export const apiService = new ApiService(); diff --git a/src/shared/api/axios.instance.ts b/src/shared/api/axios.instance.ts new file mode 100644 index 0000000..e5381c3 --- /dev/null +++ b/src/shared/api/axios.instance.ts @@ -0,0 +1,104 @@ +import axios, { + type AxiosInstance, + type AxiosResponse, + type AxiosError, + type InternalAxiosRequestConfig, +} from "axios"; + +export interface ApiResponse { + data: T; + message?: string; + status: number; +} + +class ApiClient { + private axiosInstance: AxiosInstance; + + constructor() { + this.axiosInstance = axios.create({ + baseURL: "http://213.165.213.170:8080/api/v1", + timeout: 10000, + headers: { + "Content-Type": "application/json", + }, + validateStatus: (status) => { + return status >= 200 && status < 400; + }, + // Добавляем кастомный сериализатор параметров + paramsSerializer: { + serialize: (params) => { + const parts: string[] = []; + + Object.entries(params).forEach(([key, value]) => { + if (value === undefined || value === null) return; + + if (Array.isArray(value)) { + // Преобразуем массив в множественные параметры: level=info&level=warning + value.forEach((item) => { + if (item !== undefined && item !== null) { + parts.push( + `${encodeURIComponent(key)}=${encodeURIComponent(item)}`, + ); + } + }); + } else { + parts.push( + `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`, + ); + } + }); + + return parts.join("&"); + }, + }, + }); + + this.setupInterceptors(); + } + + private setupInterceptors(): void { + this.axiosInstance.interceptors.request.use( + (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => { + // Получаем токен из localStorage + const authStorage = localStorage.getItem("auth-storage"); + if (authStorage) { + try { + const parsed = JSON.parse(authStorage); + const token = parsed.state?.token; + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + } catch (e) { + console.error("[Auth] Failed to parse auth storage:", e); + } + } + return config; + }, + (error: AxiosError): Promise => { + console.error("[Request Error]", error); + return Promise.reject(error); + }, + ); + + this.axiosInstance.interceptors.response.use( + (response: AxiosResponse): AxiosResponse => { + console.log(`[Response] ${response.status} ${response.config.url}`); + return response; + }, + async (error: AxiosError): Promise => { + if (error.response?.status === 401) { + window.location.href = "/auth"; + return Promise.reject(error); + } + + return Promise.reject(error); + }, + ); + } + + public getInstance(): AxiosInstance { + return this.axiosInstance; + } +} + +export const apiClient = new ApiClient().getInstance(); diff --git a/src/shared/api/hooks/use.api.ts b/src/shared/api/hooks/use.api.ts new file mode 100644 index 0000000..1f15b55 --- /dev/null +++ b/src/shared/api/hooks/use.api.ts @@ -0,0 +1,32 @@ +import { useState, useCallback } from "react"; + +export function useApi() { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const request = useCallback( + async (apiCall: () => Promise): Promise => { + setIsLoading(true); + setError(null); + + try { + const result = await apiCall(); + return result; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || err.message || "Произошла ошибка"; + setError(errorMessage); + return undefined; + } finally { + setIsLoading(false); + } + }, + [], + ); + + return { + isLoading, + error, + request, + }; +} diff --git a/src/shared/api/index.ts b/src/shared/api/index.ts new file mode 100644 index 0000000..c98f5ac --- /dev/null +++ b/src/shared/api/index.ts @@ -0,0 +1,2 @@ +export { apiClient } from "./axios.instance"; +export { useApi } from "./hooks/use.api"; -- 2.52.0 From d421dc8f2c2b0bacb36983e834f5c74889cdbef0 Mon Sep 17 00:00:00 2001 From: nikita Date: Fri, 12 Jun 2026 01:23:31 +0300 Subject: [PATCH 04/15] feat: themes --- package-lock.json | 10 ++ package.json | 1 + src/main.tsx | 5 +- .../theme-changer/config/theme.config.ts | 106 ++++++++++++++++++ src/modules/theme-changer/index.ts | 2 + .../provider/theme.initial.provider.tsx | 13 +++ src/modules/theme-changer/types/theme.type.ts | 13 +++ src/modules/theme-changer/ui/Theme.toggle.tsx | 68 +++++++++++ .../theme-changer/utils/apply.theme.ts | 105 +++++++++++++++++ src/pages/home.page.tsx | 12 +- 10 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 src/modules/theme-changer/config/theme.config.ts create mode 100644 src/modules/theme-changer/index.ts create mode 100644 src/modules/theme-changer/provider/theme.initial.provider.tsx create mode 100644 src/modules/theme-changer/types/theme.type.ts create mode 100644 src/modules/theme-changer/ui/Theme.toggle.tsx create mode 100644 src/modules/theme-changer/utils/apply.theme.ts diff --git a/package-lock.json b/package-lock.json index 1e76240..36414ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "axios": "^1.17.0", "react": "^19.2.6", "react-dom": "^19.2.6", + "react-icons": "^5.6.0", "react-router-dom": "^7.17.0", "tailwindcss": "^4.3.0" }, @@ -2958,6 +2959,15 @@ "react": "^19.2.7" } }, + "node_modules/react-icons": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-router": { "version": "7.17.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz", diff --git a/package.json b/package.json index f78cb78..d4442d5 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "axios": "^1.17.0", "react": "^19.2.6", "react-dom": "^19.2.6", + "react-icons": "^5.6.0", "react-router-dom": "^7.17.0", "tailwindcss": "^4.3.0" }, diff --git a/src/main.tsx b/src/main.tsx index 3d8b726..5f5373c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,9 +1,12 @@ import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import App from "./app/App.tsx"; +import { ThemeInitialProvider } from "@/modules/theme-changer"; createRoot(document.getElementById("root")!).render( - + + + , ); diff --git a/src/modules/theme-changer/config/theme.config.ts b/src/modules/theme-changer/config/theme.config.ts new file mode 100644 index 0000000..0214bfc --- /dev/null +++ b/src/modules/theme-changer/config/theme.config.ts @@ -0,0 +1,106 @@ +export const themes = [ + { + id: "light", + name: "Светлая", + description: "Чистая светлая тема", + type: "light", + colors: { + primary: "#4f46e5", + background: "#ffffff", + surface: "#f8fafc", + text: "#1f2937", + border: "#e5e7eb", + }, + }, + { + id: "dark", + name: "Темная", + description: "Элегантная темная тема", + type: "dark", + colors: { + primary: "#6366f1", + background: "#0f172a", + surface: "#1e293b", + text: "#f1f5f9", + border: "#334155", + }, + }, + { + id: "nightowl", + name: "Night Owl", + description: "Тема вдохновленная редактором кода", + type: "dark", + colors: { + primary: "#7dd3fc", + background: "#011627", + surface: "#011e3c", + text: "#d6deeb", + border: "#1d3b53", + }, + }, + { + id: "sunset", + name: "Закат", + description: "Теплые оранжевые тона", + type: "dark", + colors: { + primary: "#f97316", + background: "#1c1917", + surface: "#292524", + text: "#fafaf9", + border: "#57534e", + }, + }, + { + id: "forest", + name: "Лес", + description: "Успокаивающая зеленая тема", + type: "dark", + colors: { + primary: "#22c55e", + background: "#052e16", + surface: "#14532d", + text: "#f0fdf4", + border: "#166534", + }, + }, + { + id: "ocean", + name: "Океан", + description: "Глубокие синие тона", + type: "dark", + colors: { + primary: "#06b6d4", + background: "#164e63", + surface: "#0e7490", + text: "#f0fdfd", + border: "#0891b2", + }, + }, + { + id: "lavender", + name: "Лаванда", + description: "Нежная фиолетовая тема", + type: "light", + colors: { + primary: "#a855f7", + background: "#faf5ff", + surface: "#f3e8ff", + text: "#581c87", + border: "#e9d5ff", + }, + }, + { + id: "coffee", + name: "Кофе", + description: "Уютная коричневая тема", + type: "dark", + colors: { + primary: "#d97706", + background: "#292524", + surface: "#44403c", + text: "#f5f5f4", + border: "#57534e", + }, + }, +]; diff --git a/src/modules/theme-changer/index.ts b/src/modules/theme-changer/index.ts new file mode 100644 index 0000000..b217a25 --- /dev/null +++ b/src/modules/theme-changer/index.ts @@ -0,0 +1,2 @@ +export { ThemeInitialProvider } from "./provider/theme.initial.provider"; +export { ThemeToggle } from "./ui/Theme.toggle"; diff --git a/src/modules/theme-changer/provider/theme.initial.provider.tsx b/src/modules/theme-changer/provider/theme.initial.provider.tsx new file mode 100644 index 0000000..a70d022 --- /dev/null +++ b/src/modules/theme-changer/provider/theme.initial.provider.tsx @@ -0,0 +1,13 @@ +import { useLayoutEffect } from "react"; +import { applyTheme, initializeTheme } from "../utils/apply.theme"; + +export const ThemeInitialProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + useLayoutEffect(() => { + const theme = initializeTheme(); + applyTheme(theme); + }, []); + + return children; +}; diff --git a/src/modules/theme-changer/types/theme.type.ts b/src/modules/theme-changer/types/theme.type.ts new file mode 100644 index 0000000..5bc74d2 --- /dev/null +++ b/src/modules/theme-changer/types/theme.type.ts @@ -0,0 +1,13 @@ +export interface ITheme { + id: string; + name: string; + description: string; + type: string; + colors: { + primary: string; + background: string; + surface: string; + text: string; + border: string; + }; +} diff --git a/src/modules/theme-changer/ui/Theme.toggle.tsx b/src/modules/theme-changer/ui/Theme.toggle.tsx new file mode 100644 index 0000000..c07dc22 --- /dev/null +++ b/src/modules/theme-changer/ui/Theme.toggle.tsx @@ -0,0 +1,68 @@ +import React, { useState, useEffect } from "react"; +import { FiSun, FiMoon } from "react-icons/fi"; +import { + getCurrentTheme, + toggleDarkLight, + getSavedTheme, +} from "../../theme-changer/utils/apply.theme"; +import { themes } from "../../theme-changer/config/theme.config"; + +export const ThemeToggle: React.FC = () => { + const [currentTheme, setCurrentTheme] = useState(() => + getSavedTheme(), + ); + + const currentThemeData = themes.find((t) => t.id === currentTheme); + const isDark = currentThemeData?.type === "dark"; + + const handleClick = () => { + const newTheme = toggleDarkLight(); + setCurrentTheme(newTheme); + }; + + // Инициализация при монтировании + useEffect(() => { + const saved = getSavedTheme(); + const current = getCurrentTheme() || saved; + setCurrentTheme(current); + }, []); + + // Слушаем изменения темы из других компонентов + useEffect(() => { + const handleThemeChange = (e: Event) => { + const event = e as CustomEvent; + setCurrentTheme(event.detail.theme); + }; + window.addEventListener("themechange", handleThemeChange); + return () => window.removeEventListener("themechange", handleThemeChange); + }, []); + + return ( + + ); +}; diff --git a/src/modules/theme-changer/utils/apply.theme.ts b/src/modules/theme-changer/utils/apply.theme.ts new file mode 100644 index 0000000..3d3fb26 --- /dev/null +++ b/src/modules/theme-changer/utils/apply.theme.ts @@ -0,0 +1,105 @@ +import { themes } from "../config/theme.config"; + +export const applyTheme = (themeId: string) => { + const theme = themes.find((t) => t.id === themeId); + const root = document.documentElement; + + if (theme) { + try { + root.setAttribute("data-theme", themeId); + localStorage.setItem("theme", themeId); + localStorage.setItem("theme-type", theme.type); + + window.dispatchEvent( + new CustomEvent("themechange", { + detail: { theme: themeId, type: theme.type }, + }), + ); + } catch (error) { + console.error("❌ Error applying theme:", error); + } + } else { + console.warn(`⚠️ Theme not found: ${themeId}, falling back to light theme`); + applyTheme("light"); + } +}; + +export const getSavedTheme = () => { + try { + return localStorage.getItem("theme") || "light"; + } catch (error) { + console.error("Error reading theme from localStorage:", error); + return "light"; + } +}; + +export const initializeTheme = () => { + const savedTheme = getSavedTheme(); + + const themeExists = themes.some((t) => t.id === savedTheme); + const themeToApply = themeExists ? savedTheme : "light"; + + applyTheme(themeToApply); + return themeToApply; +}; + +export const getCurrentTheme = () => { + return document.documentElement.getAttribute("data-theme") || "light"; +}; + +export const getCurrentThemeType = () => { + const currentTheme = getCurrentTheme(); + const theme = themes.find((t) => t.id === currentTheme); + return theme ? theme.type : "light"; +}; + +export const toggleDarkLight = () => { + const currentTheme = getCurrentTheme(); + const currentThemeData = themes.find((t) => t.id === currentTheme); + + if (currentThemeData) { + const oppositeThemes = themes.filter( + (t) => t.type !== currentThemeData.type, + ); + if (oppositeThemes.length > 0) { + applyTheme(oppositeThemes[0].id); + return oppositeThemes[0].id; + } + } + + const newTheme = currentTheme === "light" ? "dark" : "light"; + applyTheme(newTheme); + return newTheme; +}; + +export const getNextTheme = () => { + const currentTheme = getCurrentTheme(); + const currentIndex = themes.findIndex((t) => t.id === currentTheme); + const nextIndex = (currentIndex + 1) % themes.length; + return themes[nextIndex].id; +}; + +export const applySystemTheme = () => { + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + applyTheme("dark"); + } else { + applyTheme("light"); + } +}; + +export const watchSystemTheme = () => { + if (window.matchMedia) { + window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", (e) => { + if (e.matches) { + applyTheme("dark"); + } else { + applyTheme("light"); + } + }); + } +}; diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 58fd7c1..df87d99 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -1,3 +1,13 @@ +import { ThemeToggle } from "@/modules/theme-changer/ui/Theme.toggle"; + export const HomePage = () => { - return
Домашняя
; + return ( +
+ + Домашняя +
+ ); }; -- 2.52.0 From b8247953892620797455b208d61be7b536eaf275 Mon Sep 17 00:00:00 2001 From: NikitaTorbenko <2015nekitciti@gmail.com> Date: Fri, 12 Jun 2026 18:25:29 +0300 Subject: [PATCH 05/15] feat --- yarn.lock | 1614 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1614 insertions(+) create mode 100644 yarn.lock diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..408b0ad --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1614 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7" + integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw== + dependencies: + "@babel/helper-validator-identifier" "^7.29.7" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629" + integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg== + +"@babel/core@^7.24.4": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7" + integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA== + dependencies: + "@babel/code-frame" "^7.29.7" + "@babel/generator" "^7.29.7" + "@babel/helper-compilation-targets" "^7.29.7" + "@babel/helper-module-transforms" "^7.29.7" + "@babel/helpers" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/template" "^7.29.7" + "@babel/traverse" "^7.29.7" + "@babel/types" "^7.29.7" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3" + integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ== + dependencies: + "@babel/parser" "^7.29.7" + "@babel/types" "^7.29.7" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042" + integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g== + dependencies: + "@babel/compat-data" "^7.29.7" + "@babel/helper-validator-option" "^7.29.7" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b" + integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA== + +"@babel/helper-module-imports@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396" + integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g== + dependencies: + "@babel/traverse" "^7.29.7" + "@babel/types" "^7.29.7" + +"@babel/helper-module-transforms@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae" + integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg== + dependencies: + "@babel/helper-module-imports" "^7.29.7" + "@babel/helper-validator-identifier" "^7.29.7" + "@babel/traverse" "^7.29.7" + +"@babel/helper-string-parser@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f" + integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw== + +"@babel/helper-validator-identifier@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" + integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== + +"@babel/helper-validator-option@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a" + integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw== + +"@babel/helpers@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607" + integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg== + dependencies: + "@babel/template" "^7.29.7" + "@babel/types" "^7.29.7" + +"@babel/parser@^7.24.4", "@babel/parser@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334" + integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg== + dependencies: + "@babel/types" "^7.29.7" + +"@babel/template@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700" + integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg== + dependencies: + "@babel/code-frame" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/types" "^7.29.7" + +"@babel/traverse@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d" + integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw== + dependencies: + "@babel/code-frame" "^7.29.7" + "@babel/generator" "^7.29.7" + "@babel/helper-globals" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/template" "^7.29.7" + "@babel/types" "^7.29.7" + debug "^4.3.1" + +"@babel/types@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92" + integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA== + dependencies: + "@babel/helper-string-parser" "^7.29.7" + "@babel/helper-validator-identifier" "^7.29.7" + +"@emnapi/core@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467" + integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw== + dependencies: + "@emnapi/wasi-threads" "1.2.1" + tslib "^2.4.0" + +"@emnapi/core@^1.10.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.11.0.tgz#8a655042dbbb10d0266670c9903c34a7001c705b" + integrity sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q== + dependencies: + "@emnapi/wasi-threads" "1.2.2" + tslib "^2.4.0" + +"@emnapi/runtime@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c" + integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA== + dependencies: + tslib "^2.4.0" + +"@emnapi/runtime@^1.10.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.11.0.tgz#ce16b3674ff7266bbf50f9668bde8a04f3014d4e" + integrity sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz#28fed21a1ba1ce797c44a070abc94d42f3ae8548" + integrity sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.2.2", "@emnapi/wasi-threads@^1.2.1": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz#4c93becf5bfa3b13d1bbdcc06aee38321ad8139a" + integrity sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA== + dependencies: + tslib "^2.4.0" + +"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.12.2": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/config-array@^0.23.5": + version "0.23.5" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.5.tgz#56e86d243049195d8acc0c06a1b3dfdc3fa3de95" + integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA== + dependencies: + "@eslint/object-schema" "^3.0.5" + debug "^4.3.1" + minimatch "^10.2.4" + +"@eslint/config-helpers@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.6.0.tgz#ef9a36881d39dfd5dbeac22b0da997fabfb08b03" + integrity sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA== + dependencies: + "@eslint/core" "^1.2.1" + +"@eslint/core@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.2.1.tgz#c1da7cd1b82fa8787f98b5629fb811848a1b63ce" + integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/js@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583" + integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA== + +"@eslint/object-schema@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.5.tgz#88e9bf4d11d2b19c082e78ebe7ce88724a5eb091" + integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw== + +"@eslint/plugin-kit@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz#4b0962f3f2c7ce8bc98b3ecfe34525c09d2cb729" + integrity sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A== + dependencies: + "@eslint/core" "^1.2.1" + levn "^0.4.1" + +"@humanfs/core@^0.19.2": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.2.tgz#a8272ca03b2acf492670222b2320b6c421bfde60" + integrity sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA== + dependencies: + "@humanfs/types" "^0.15.0" + +"@humanfs/node@^0.16.6": + version "0.16.8" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.8.tgz#8f800cccc13f4f8cd3116e2d9c0a94939da3e3ed" + integrity sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ== + dependencies: + "@humanfs/core" "^0.19.2" + "@humanfs/types" "^0.15.0" + "@humanwhocodes/retry" "^0.4.0" + +"@humanfs/types@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@humanfs/types/-/types-0.15.0.tgz#f2a09f62012390b2bff3fc6fb248ddec8c09a090" + integrity sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q== + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@napi-rs/wasm-runtime@^1.1.4": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz#cccd6ebc40b991dea6936f9126b1b8155b6c4c95" + integrity sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q== + dependencies: + "@tybys/wasm-util" "^0.10.2" + +"@oxc-project/types@=0.133.0": + version "0.133.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.133.0.tgz#2e282ef9e1d26e06b68ccd14b73f310a3b2cf7f8" + integrity sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA== + +"@rolldown/binding-android-arm64@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz#54ce8f8382213f4a314a0c2f7ba83f81ffeae592" + integrity sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw== + +"@rolldown/binding-darwin-arm64@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz#388fca1566c14c00c4b446fc3928630e7f0d95fc" + integrity sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA== + +"@rolldown/binding-darwin-x64@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz#53f57de1f599ecf1db13823cfc88c18fb80954ad" + integrity sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg== + +"@rolldown/binding-freebsd-x64@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz#6f3fdda1b7aeaac9d268a526804b4fb96e4e35f1" + integrity sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g== + +"@rolldown/binding-linux-arm-gnueabihf@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz#d87a454bf585cc9676849377e91d6e375297326f" + integrity sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw== + +"@rolldown/binding-linux-arm64-gnu@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz#419fd6bf612cf348f10528cbcd94ebab9607d8d1" + integrity sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw== + +"@rolldown/binding-linux-arm64-musl@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz#fcc6918696bb76844877e1e4930a18fd0d374069" + integrity sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q== + +"@rolldown/binding-linux-ppc64-gnu@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz#32aecb7c8dae5d4f2a8cde57a058ec86991542f8" + integrity sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg== + +"@rolldown/binding-linux-s390x-gnu@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz#bed9346ea81e6bb8b93cf11f5d88b77db890b763" + integrity sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg== + +"@rolldown/binding-linux-x64-gnu@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz#64c2d26f75dffd9b5a1f97557a00ae77250c8cb7" + integrity sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg== + +"@rolldown/binding-linux-x64-musl@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz#5a45132e8a47659eeaaf3b540c2954a97c860ff3" + integrity sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow== + +"@rolldown/binding-openharmony-arm64@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz#290513068c55e849dc8457a32afee1d7b0acb309" + integrity sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg== + +"@rolldown/binding-wasm32-wasi@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz#3d9972dbf1a953d3c7afaa4a0f20ef2b2e39f31b" + integrity sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg== + dependencies: + "@emnapi/core" "1.10.0" + "@emnapi/runtime" "1.10.0" + "@napi-rs/wasm-runtime" "^1.1.4" + +"@rolldown/binding-win32-arm64-msvc@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz#a004ab607a16d6f03bcb555728ff888af75773ad" + integrity sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g== + +"@rolldown/binding-win32-x64-msvc@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz#e2a25b34691a1cc8a1209d7de709063026dd0cdb" + integrity sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA== + +"@rolldown/pluginutils@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz#e3fcee093fbb5ce765e1ad088ff4de2889f6f9be" + integrity sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw== + +"@tailwindcss/node@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.3.0.tgz#9dc5312bf41c48658529f36021e0b466c4eb7860" + integrity sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g== + dependencies: + "@jridgewell/remapping" "^2.3.5" + enhanced-resolve "^5.21.0" + jiti "^2.6.1" + lightningcss "1.32.0" + magic-string "^0.30.21" + source-map-js "^1.2.1" + tailwindcss "4.3.0" + +"@tailwindcss/oxide-android-arm64@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz#e4533b6125236fe81a899cf5a82028c85244def8" + integrity sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng== + +"@tailwindcss/oxide-darwin-arm64@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz#96b074ef64ec6c41d580063740c8d36cf5c459ce" + integrity sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ== + +"@tailwindcss/oxide-darwin-x64@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz#0d9638d06d38684339b2dc06631966a7296bb64e" + integrity sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA== + +"@tailwindcss/oxide-freebsd-x64@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz#efc7acd17cd38d7585c07cb938a4f1b703f79d7a" + integrity sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ== + +"@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz#e41c945e529670cd93fd6ed0c6a2880de5c40333" + integrity sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA== + +"@tailwindcss/oxide-linux-arm64-gnu@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz#6bb608b16ba7146d61097c2f4c7ee927d1f3580a" + integrity sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg== + +"@tailwindcss/oxide-linux-arm64-musl@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz#1bb443aa371bb99b50cb39d4d688151fadcd8a63" + integrity sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ== + +"@tailwindcss/oxide-linux-x64-gnu@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz#5267c0bb2597426c0d2e759acb5389cde2aa71fd" + integrity sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ== + +"@tailwindcss/oxide-linux-x64-musl@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz#fb2da97c67b218e5c7c723cb32782d55d7e4a5d5" + integrity sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg== + +"@tailwindcss/oxide-wasm32-wasi@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz#3f6538e511066d67d8683863dcaeeb16c22de849" + integrity sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA== + dependencies: + "@emnapi/core" "^1.10.0" + "@emnapi/runtime" "^1.10.0" + "@emnapi/wasi-threads" "^1.2.1" + "@napi-rs/wasm-runtime" "^1.1.4" + "@tybys/wasm-util" "^0.10.1" + tslib "^2.8.1" + +"@tailwindcss/oxide-win32-arm64-msvc@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz#ec45fba773c76759338c05d4fe5cf42c4eea2e4e" + integrity sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ== + +"@tailwindcss/oxide-win32-x64-msvc@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz#58cdd6e06adbe2e3160274edfcd0b0b43e17fee4" + integrity sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA== + +"@tailwindcss/oxide@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.3.0.tgz#cc1c61e88f62c0e9f56062de3e7873acaa2159d4" + integrity sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg== + optionalDependencies: + "@tailwindcss/oxide-android-arm64" "4.3.0" + "@tailwindcss/oxide-darwin-arm64" "4.3.0" + "@tailwindcss/oxide-darwin-x64" "4.3.0" + "@tailwindcss/oxide-freebsd-x64" "4.3.0" + "@tailwindcss/oxide-linux-arm-gnueabihf" "4.3.0" + "@tailwindcss/oxide-linux-arm64-gnu" "4.3.0" + "@tailwindcss/oxide-linux-arm64-musl" "4.3.0" + "@tailwindcss/oxide-linux-x64-gnu" "4.3.0" + "@tailwindcss/oxide-linux-x64-musl" "4.3.0" + "@tailwindcss/oxide-wasm32-wasi" "4.3.0" + "@tailwindcss/oxide-win32-arm64-msvc" "4.3.0" + "@tailwindcss/oxide-win32-x64-msvc" "4.3.0" + +"@tailwindcss/vite@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@tailwindcss/vite/-/vite-4.3.0.tgz#b2bbc069a4c700ea7aef5ee30416d84b7652e136" + integrity sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw== + dependencies: + "@tailwindcss/node" "4.3.0" + "@tailwindcss/oxide" "4.3.0" + tailwindcss "4.3.0" + +"@tybys/wasm-util@^0.10.1", "@tybys/wasm-util@^0.10.2": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.2.tgz#12b3a1b33db1f9cad4ddff1f604ab7dd00bf464e" + integrity sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg== + dependencies: + tslib "^2.4.0" + +"@types/esrecurse@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec" + integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== + +"@types/estree@^1.0.6", "@types/estree@^1.0.8": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" + integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@^24.12.3": + version "24.13.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.13.2.tgz#3b9b280a7055128359f125eb1067d9a190f39854" + integrity sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA== + dependencies: + undici-types "~7.18.0" + +"@types/react-dom@^19.2.3": + version "19.2.3" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c" + integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== + +"@types/react@^19.2.14": + version "19.2.17" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.17.tgz#dccac365baa0f1734ec270ff4b51c89465e8dc7f" + integrity sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw== + dependencies: + csstype "^3.2.2" + +"@typescript-eslint/eslint-plugin@8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz#db20271974b94a3a54d3b9544e5f5b3481448400" + integrity sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.61.0" + "@typescript-eslint/type-utils" "8.61.0" + "@typescript-eslint/utils" "8.61.0" + "@typescript-eslint/visitor-keys" "8.61.0" + ignore "^7.0.5" + natural-compare "^1.4.0" + ts-api-utils "^2.5.0" + +"@typescript-eslint/parser@8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.61.0.tgz#1afe73c9ccce16b7a26d6b95f9400b0ccc34af87" + integrity sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w== + dependencies: + "@typescript-eslint/scope-manager" "8.61.0" + "@typescript-eslint/types" "8.61.0" + "@typescript-eslint/typescript-estree" "8.61.0" + "@typescript-eslint/visitor-keys" "8.61.0" + debug "^4.4.3" + +"@typescript-eslint/project-service@8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.61.0.tgz#417a2feac32e8ebd336d63f068c3b42b736ea1ac" + integrity sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.61.0" + "@typescript-eslint/types" "^8.61.0" + debug "^4.4.3" + +"@typescript-eslint/scope-manager@8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz#93c2520d05653fe65eb9ee98efc74fd0134a7852" + integrity sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA== + dependencies: + "@typescript-eslint/types" "8.61.0" + "@typescript-eslint/visitor-keys" "8.61.0" + +"@typescript-eslint/tsconfig-utils@8.61.0", "@typescript-eslint/tsconfig-utils@^8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz#05d6e3ff20001674ebcd22d03dac29ee448043ba" + integrity sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ== + +"@typescript-eslint/type-utils@8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz#50219b57e6b89cecfb1a15f093b15ec9ee019974" + integrity sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A== + dependencies: + "@typescript-eslint/types" "8.61.0" + "@typescript-eslint/typescript-estree" "8.61.0" + "@typescript-eslint/utils" "8.61.0" + debug "^4.4.3" + ts-api-utils "^2.5.0" + +"@typescript-eslint/types@8.61.0", "@typescript-eslint/types@^8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.61.0.tgz#0ddb46e012a4288292950bdd253db42f278ce64d" + integrity sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg== + +"@typescript-eslint/typescript-estree@8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz#98ca47260bbf627fc28f018b3a0abf00e3090690" + integrity sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA== + dependencies: + "@typescript-eslint/project-service" "8.61.0" + "@typescript-eslint/tsconfig-utils" "8.61.0" + "@typescript-eslint/types" "8.61.0" + "@typescript-eslint/visitor-keys" "8.61.0" + debug "^4.4.3" + minimatch "^10.2.2" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.5.0" + +"@typescript-eslint/utils@8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.61.0.tgz#ed3546a052787e84ea6c5064d0919fc5eea8522f" + integrity sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.61.0" + "@typescript-eslint/types" "8.61.0" + "@typescript-eslint/typescript-estree" "8.61.0" + +"@typescript-eslint/visitor-keys@8.61.0": + version "8.61.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz#39b4e1ab8936d23bea973d39fd092f9aa21f275e" + integrity sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ== + dependencies: + "@typescript-eslint/types" "8.61.0" + eslint-visitor-keys "^5.0.0" + +"@vitejs/plugin-react@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz#f70cb8ed0ce225dbc3055d78070f820d8aa35eda" + integrity sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg== + dependencies: + "@rolldown/pluginutils" "^1.0.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.16.0: + version "8.17.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.17.0.tgz#1785adb84faf8d8add10369b93826fc2bd08f1fe" + integrity sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ajv@^6.14.0: + version "6.15.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492" + integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.17.0.tgz#ae5a1164a4f719942cd73c67e6a3f62d3ccb8f2b" + integrity sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw== + dependencies: + follow-redirects "^1.16.0" + form-data "^4.0.5" + https-proxy-agent "^5.0.1" + proxy-from-env "^2.1.0" + +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + +baseline-browser-mapping@^2.10.12: + version "2.10.36" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.36.tgz#ff3c7c87095c70075b1ac787160fd4fc98750a74" + integrity sha512-lVq/Df7LXlO79MVaaUHztSwWiG9oXoWHlgvNS51v8Dpd4+G4/VIy6qYePTw31nAVls33nUtnfezYeLkYAak9dg== + +brace-expansion@^5.0.5: + version "5.0.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285" + integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== + dependencies: + balanced-match "^4.0.2" + +browserslist@^4.24.0: + version "4.28.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2" + integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg== + dependencies: + baseline-browser-mapping "^2.10.12" + caniuse-lite "^1.0.30001782" + electron-to-chromium "^1.5.328" + node-releases "^2.0.36" + update-browserslist-db "^1.2.3" + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +caniuse-lite@^1.0.30001782: + version "1.0.30001799" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz#5c909138c27f1a61219d3e092071c1cc7d32dc55" + integrity sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" + integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + +debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +detect-libc@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +electron-to-chromium@^1.5.328: + version "1.5.372" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz#ae8ac69942a37b231773b8fb01759f73733599e3" + integrity sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA== + +enhanced-resolve@^5.21.0: + version "5.24.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz#cf14b9768a774cb6a5087220c0dc6e55df6ec35a" + integrity sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.3.3" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b" + integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-react-hooks@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz#e6742cad75d970c0a3f30d7d3fa80a4784f55927" + integrity sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g== + dependencies: + "@babel/core" "^7.24.4" + "@babel/parser" "^7.24.4" + hermes-parser "^0.25.1" + zod "^3.25.0 || ^4.0.0" + zod-validation-error "^3.5.0 || ^4.0.0" + +eslint-plugin-react-refresh@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz#39e11021be10e1cd9adab2bdeabc65b17222409f" + integrity sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA== + +eslint-scope@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.2.tgz#b9de6ace2fab1cff24d2e58d85b74c8fcea39802" + integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ== + dependencies: + "@types/esrecurse" "^4.3.1" + "@types/estree" "^1.0.8" + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + +eslint@^10.3.0: + version "10.4.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.4.1.tgz#f6640b176e0912246d9ddbf8fcfa5e8b7f02445a" + integrity sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.2" + "@eslint/config-array" "^0.23.5" + "@eslint/config-helpers" "^0.6.0" + "@eslint/core" "^1.2.1" + "@eslint/plugin-kit" "^0.7.2" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.14.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^9.1.2" + eslint-visitor-keys "^5.0.1" + espree "^11.2.0" + esquery "^1.7.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + minimatch "^10.2.4" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-11.2.0.tgz#01d5e47dc332aaba3059008362454a8cc34ccaa5" + integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== + dependencies: + acorn "^8.16.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^5.0.1" + +esquery@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== + +follow-redirects@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" + integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== + +form-data@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +globals@^17.6.0: + version "17.6.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.6.0.tgz#0f0be018d5cca8690e6375ead1f65c4bb96191fc" + integrity sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA== + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" + integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== + dependencies: + function-bind "^1.1.2" + +hermes-estree@0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" + integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== + +hermes-parser@^0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" + integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== + dependencies: + hermes-estree "0.25.1" + +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jiti@^2.6.1: + version "2.7.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.7.0.tgz#974228f2f4ca2bc21885a1797b45fea68e950c64" + integrity sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lightningcss-android-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz#f033885116dfefd9c6f54787523e3514b61e1968" + integrity sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg== + +lightningcss-darwin-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz#50b71871b01c8199584b649e292547faea7af9b5" + integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== + +lightningcss-darwin-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz#35f3e97332d130b9ca181e11b568ded6aebc6d5e" + integrity sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w== + +lightningcss-freebsd-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz#9777a76472b64ed6ff94342ad64c7bafd794a575" + integrity sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig== + +lightningcss-linux-arm-gnueabihf@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz#13ae652e1ab73b9135d7b7da172f666c410ad53d" + integrity sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw== + +lightningcss-linux-arm64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz#417858795a94592f680123a1b1f9da8a0e1ef335" + integrity sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ== + +lightningcss-linux-arm64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz#6be36692e810b718040802fd809623cffe732133" + integrity sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg== + +lightningcss-linux-x64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz#0b7803af4eb21cfd38dd39fe2abbb53c7dd091f6" + integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA== + +lightningcss-linux-x64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz#88dc8ba865ddddb1ac5ef04b0f161804418c163b" + integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg== + +lightningcss-win32-arm64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz#4f30ba3fa5e925f5b79f945e8cc0d176c3b1ab38" + integrity sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw== + +lightningcss-win32-x64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz#141aa5605645064928902bb4af045fa7d9f4220a" + integrity sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q== + +lightningcss@1.32.0, lightningcss@^1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.32.0.tgz#b85aae96486dcb1bf49a7c8571221273f4f1e4a9" + integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== + dependencies: + detect-libc "^2.0.3" + optionalDependencies: + lightningcss-android-arm64 "1.32.0" + lightningcss-darwin-arm64 "1.32.0" + lightningcss-darwin-x64 "1.32.0" + lightningcss-freebsd-x64 "1.32.0" + lightningcss-linux-arm-gnueabihf "1.32.0" + lightningcss-linux-arm64-gnu "1.32.0" + lightningcss-linux-arm64-musl "1.32.0" + lightningcss-linux-x64-gnu "1.32.0" + lightningcss-linux-x64-musl "1.32.0" + lightningcss-win32-arm64-msvc "1.32.0" + lightningcss-win32-x64-msvc "1.32.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +magic-string@^0.30.21: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@^10.2.2, minimatch@^10.2.4: + version "10.2.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== + dependencies: + brace-expansion "^5.0.5" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@^3.3.12: + version "3.3.12" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.12.tgz#ab3d912e217a6d0a514f00a72a16543a28982c05" + integrity sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-releases@^2.0.36: + version "2.0.47" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.47.tgz#521bb2786da8eb140b748841c0b3b3a75334ffc4" + integrity sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og== + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + +postcss@^8.5.15: + version "8.5.15" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.15.tgz#d1eaf677a324e9ec02196da2d3fecf4a0b9a735c" + integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A== + dependencies: + nanoid "^3.3.12" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +proxy-from-env@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba" + integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +react-dom@^19.2.6: + version "19.2.7" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.7.tgz#0450dc9ae9ddbff76ef196401cd8b8c7fb466ccc" + integrity sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ== + dependencies: + scheduler "^0.27.0" + +react-icons@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.6.0.tgz#27bcc4acbc836e762548d76041cf9b9fef4e3837" + integrity sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA== + +react-router-dom@^7.17.0: + version "7.17.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.17.0.tgz#e77527b4b7862f7b47ff26dd5b9315fb897b82a7" + integrity sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw== + dependencies: + react-router "7.17.0" + +react-router@7.17.0: + version "7.17.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.17.0.tgz#88bbe817c6e37ab36faf140623b5d4678bf81e41" + integrity sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ== + dependencies: + cookie "^1.0.1" + set-cookie-parser "^2.6.0" + +react@^19.2.6: + version "19.2.7" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.7.tgz#1f47a1bfc06f8ec885752c6f4af14369a9f8260b" + integrity sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ== + +rolldown@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.3.tgz#db88a3008fb0e28230a00423727ce75ba32121ac" + integrity sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g== + dependencies: + "@oxc-project/types" "=0.133.0" + "@rolldown/pluginutils" "^1.0.0" + optionalDependencies: + "@rolldown/binding-android-arm64" "1.0.3" + "@rolldown/binding-darwin-arm64" "1.0.3" + "@rolldown/binding-darwin-x64" "1.0.3" + "@rolldown/binding-freebsd-x64" "1.0.3" + "@rolldown/binding-linux-arm-gnueabihf" "1.0.3" + "@rolldown/binding-linux-arm64-gnu" "1.0.3" + "@rolldown/binding-linux-arm64-musl" "1.0.3" + "@rolldown/binding-linux-ppc64-gnu" "1.0.3" + "@rolldown/binding-linux-s390x-gnu" "1.0.3" + "@rolldown/binding-linux-x64-gnu" "1.0.3" + "@rolldown/binding-linux-x64-musl" "1.0.3" + "@rolldown/binding-openharmony-arm64" "1.0.3" + "@rolldown/binding-wasm32-wasi" "1.0.3" + "@rolldown/binding-win32-arm64-msvc" "1.0.3" + "@rolldown/binding-win32-x64-msvc" "1.0.3" + +scheduler@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.7.3: + version "7.8.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.4.tgz#c73eceebae0616934be8dff28a7fd70757c8e696" + integrity sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA== + +set-cookie-parser@^2.6.0: + version "2.7.2" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" + integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +tailwindcss@4.3.0, tailwindcss@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.3.0.tgz#0a874e044a859cf6de413f3a59e76a9bedf05264" + integrity sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q== + +tapable@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160" + integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A== + +tinyglobby@^0.2.15, tinyglobby@^0.2.17: + version "0.2.17" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631" + integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.4" + +ts-api-utils@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1" + integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== + +tslib@^2.4.0, tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +typescript-eslint@^8.59.2: + version "8.61.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.61.0.tgz#6927fb94f5f29623e370d33fd9fa61f15d6d996b" + integrity sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw== + dependencies: + "@typescript-eslint/eslint-plugin" "8.61.0" + "@typescript-eslint/parser" "8.61.0" + "@typescript-eslint/typescript-estree" "8.61.0" + "@typescript-eslint/utils" "8.61.0" + +typescript@~6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== + +undici-types@~7.18.0: + version "7.18.2" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" + integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== + +update-browserslist-db@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +vite@^8.0.12: + version "8.0.16" + resolved "https://registry.yarnpkg.com/vite/-/vite-8.0.16.tgz#ae073866c06563d6634a90169a496e11bd84f1a6" + integrity sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw== + dependencies: + lightningcss "^1.32.0" + picomatch "^4.0.4" + postcss "^8.5.15" + rolldown "1.0.3" + tinyglobby "^0.2.17" + optionalDependencies: + fsevents "~2.3.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +"zod-validation-error@^3.5.0 || ^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918" + integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ== + +"zod@^3.25.0 || ^4.0.0": + version "4.4.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.4.3.tgz#b680f172885d18bbebf21a834ea25e55a1bbf356" + integrity sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ== -- 2.52.0 From 444bc05f9d01d7e9c0e37bc18770a05f2dd52785 Mon Sep 17 00:00:00 2001 From: NikitaTorbenko <2015nekitciti@gmail.com> Date: Fri, 12 Jun 2026 18:57:58 +0300 Subject: [PATCH 06/15] feat: create login register --- src/app/providers/helper/protected.route.tsx | 5 +- src/app/providers/routing.tsx | 37 ++- src/modules/auth/api/auth.service.ts | 110 +++++++++ .../components/CreateOrganizationForm.tsx | 138 +++++++++++ src/modules/auth/components/LoginForm.tsx | 109 +++++++++ src/modules/auth/components/RegisterForm.tsx | 231 ++++++++++++++++++ src/modules/auth/hooks/useAuth.ts | 82 +++++++ src/modules/auth/types/auth.types.ts | 38 +++ src/pages/AuthPage.tsx | 50 ++++ src/pages/CreateOrganizationPage.tsx | 52 ++++ 10 files changed, 838 insertions(+), 14 deletions(-) create mode 100644 src/modules/auth/api/auth.service.ts create mode 100644 src/modules/auth/components/CreateOrganizationForm.tsx create mode 100644 src/modules/auth/components/LoginForm.tsx create mode 100644 src/modules/auth/components/RegisterForm.tsx create mode 100644 src/modules/auth/hooks/useAuth.ts create mode 100644 src/modules/auth/types/auth.types.ts create mode 100644 src/pages/AuthPage.tsx create mode 100644 src/pages/CreateOrganizationPage.tsx diff --git a/src/app/providers/helper/protected.route.tsx b/src/app/providers/helper/protected.route.tsx index 67fa1b3..f803ad1 100644 --- a/src/app/providers/helper/protected.route.tsx +++ b/src/app/providers/helper/protected.route.tsx @@ -1,4 +1,5 @@ import { Navigate } from "react-router-dom"; +import { authService } from "@/modules/auth/api/auth.service"; interface ProtectedRouteProps { children: React.ReactNode; @@ -9,7 +10,9 @@ export const ProtectedRoute: React.FC = ({ children, fallbackPath = "/", }) => { - if (false) { + const isAuthenticated = authService.isAuthenticated(); + + if (!isAuthenticated) { return ; } diff --git a/src/app/providers/routing.tsx b/src/app/providers/routing.tsx index d515bba..7b1691e 100644 --- a/src/app/providers/routing.tsx +++ b/src/app/providers/routing.tsx @@ -1,8 +1,10 @@ import { Suspense } from "react"; +import { Routes as ReactRoutes, Route } from "react-router-dom"; import { HomePage } from "@/pages/home.page"; import { SecondaryPage } from "@/pages/secondary.page"; +import { AuthPage } from "@/pages/AuthPage"; +import { CreateOrganizationPage } from "@/pages/CreateOrganizationPage"; import { ProtectedRoute } from "./helper/protected.route"; -import { Routes as ReactRoutes, Route } from "react-router-dom"; export const Routing = () => { return ( @@ -14,18 +16,27 @@ export const Routing = () => { } > - } /> - - - - - - } - /> - + } /> + } + /> + + + + } + /> + + + + } + /> ); diff --git a/src/modules/auth/api/auth.service.ts b/src/modules/auth/api/auth.service.ts new file mode 100644 index 0000000..18cb94c --- /dev/null +++ b/src/modules/auth/api/auth.service.ts @@ -0,0 +1,110 @@ +import { apiService } from "@/shared/api/api.service"; +import type { + LoginCredentials, + RegisterData, + AuthResponse, + OrganizationCreateData, + OrganizationResponse, +} from "../types/auth.types"; + +export const authService = { + async login(credentials: LoginCredentials): Promise { + const formData = new URLSearchParams(); + formData.append("username", credentials.username); + formData.append("password", credentials.password); + + return apiService.post("/auth/login", formData, { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }); + }, + + async register( + data: Omit, + ): Promise { + return apiService.post("/auth/register", { + username: data.username, + email: data.email, + password: data.password, + first_name: data.first_name, + last_name: data.last_name, + }); + }, + + async createOrganization( + data: OrganizationCreateData, + ): Promise { + const formData = new FormData(); + formData.append("name", data.name); + if (data.logo) { + formData.append("logo", data.logo); + } + + return apiService.post("/organizations", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + }, + + async getCurrentUser(): Promise { + return apiService.get("/users/me"); + }, + + async getOrganizations(): Promise { + return apiService.get("/organizations"); + }, + + logout(): void { + localStorage.removeItem("auth-storage"); + localStorage.removeItem("organization-storage"); + }, + + saveAuthData(token: string, user: AuthResponse["user"]): void { + const authStorage = { + state: { + token, + user, + }, + version: 0, + }; + localStorage.setItem("auth-storage", JSON.stringify(authStorage)); + }, + + saveOrganization(organization: OrganizationResponse): void { + localStorage.setItem("organization-storage", JSON.stringify(organization)); + }, + + getToken(): string | null { + const authStorage = localStorage.getItem("auth-storage"); + if (!authStorage) return null; + + try { + const parsed = JSON.parse(authStorage); + return parsed.state?.token || null; + } catch { + return null; + } + }, + + getCurrentOrganization(): OrganizationResponse | null { + const orgStorage = localStorage.getItem("organization-storage"); + if (!orgStorage) return null; + + try { + return JSON.parse(orgStorage); + } catch { + return null; + } + }, + + isAuthenticated(): boolean { + const token = this.getToken(); + return !!token; + }, + + hasOrganization(): boolean { + return !!this.getCurrentOrganization(); + }, +}; diff --git a/src/modules/auth/components/CreateOrganizationForm.tsx b/src/modules/auth/components/CreateOrganizationForm.tsx new file mode 100644 index 0000000..4477f39 --- /dev/null +++ b/src/modules/auth/components/CreateOrganizationForm.tsx @@ -0,0 +1,138 @@ +import React, { useState, useRef } from "react"; +import { useAuth } from "../hooks/useAuth"; + +export const CreateOrganizationForm = () => { + const [organizationName, setOrganizationName] = useState(""); + const [logo, setLogo] = useState(null); + const [logoPreview, setLogoPreview] = useState(""); + const fileInputRef = useRef(null); + const { createOrganization, isLoading, error } = useAuth(); + + const handleLogoChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + setLogo(file); + const reader = new FileReader(); + reader.onloadend = () => { + setLogoPreview(reader.result as string); + }; + reader.readAsDataURL(file); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (organizationName.trim()) { + await createOrganization({ + name: organizationName, + logo: logo || undefined, + }); + } + }; + + return ( +
+
+
+
fileInputRef.current?.click()} + > + {logoPreview ? ( + Organization logo + ) : ( +
+ + + + + Загрузить лого + +
+ )} +
+ +
+
+ +
+ + setOrganizationName(e.target.value)} + className="w-full px-4 py-2 rounded-lg border transition-all focus:outline-none focus:ring-2" + style={{ + backgroundColor: "var(--input-bg)", + color: "var(--text-primary)", + borderColor: "var(--border)", + "--tw-ring-color": "var(--accent)", + }} + required + disabled={isLoading} + placeholder="Введите название организации" + /> +
+ + {error && ( +
+ {error} +
+ )} + + +
+ ); +}; diff --git a/src/modules/auth/components/LoginForm.tsx b/src/modules/auth/components/LoginForm.tsx new file mode 100644 index 0000000..9a14f78 --- /dev/null +++ b/src/modules/auth/components/LoginForm.tsx @@ -0,0 +1,109 @@ +import React, { useState } from "react"; +import { useAuth } from "../hooks/useAuth"; + +interface LoginFormProps { + onSwitchToRegister: () => void; +} + +export const LoginForm: React.FC = ({ onSwitchToRegister }) => { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const { login, isLoading, error } = useAuth(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + await login({ username, password }); + }; + + return ( +
+
+ + setUsername(e.target.value)} + className="w-full px-4 py-2 rounded-lg border transition-all focus:outline-none focus:ring-2" + style={{ + backgroundColor: "var(--input-bg)", + color: "var(--text-primary)", + borderColor: "var(--border)", + "--tw-ring-color": "var(--accent)", + }} + required + disabled={isLoading} + placeholder="Введите имя пользователя" + /> +
+ +
+ + setPassword(e.target.value)} + className="w-full px-4 py-2 rounded-lg border transition-all focus:outline-none focus:ring-2" + style={{ + backgroundColor: "var(--input-bg)", + color: "var(--text-primary)", + borderColor: "var(--border)", + "--tw-ring-color": "var(--accent)", + }} + required + disabled={isLoading} + placeholder="Введите пароль" + /> +
+ + {error && ( +
+ {error} +
+ )} + + + +
+ +
+
+ ); +}; diff --git a/src/modules/auth/components/RegisterForm.tsx b/src/modules/auth/components/RegisterForm.tsx new file mode 100644 index 0000000..7033db6 --- /dev/null +++ b/src/modules/auth/components/RegisterForm.tsx @@ -0,0 +1,231 @@ +import React, { useState } from "react"; +import { useAuth } from "../hooks/useAuth"; + +interface RegisterFormProps { + onSwitchToLogin: () => void; +} + +export const RegisterForm: React.FC = ({ + onSwitchToLogin, +}) => { + const [formData, setFormData] = useState({ + first_name: "", + last_name: "", + username: "", + email: "", + password: "", + confirmPassword: "", + }); + + const { register, isLoading, error } = useAuth(); + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ + ...formData, + [e.target.id]: e.target.value, + }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + await register(formData); + }; + + return ( +
+
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + {error && ( +
+ {error} +
+ )} + + + +
+ +
+
+ ); +}; diff --git a/src/modules/auth/hooks/useAuth.ts b/src/modules/auth/hooks/useAuth.ts new file mode 100644 index 0000000..3d72584 --- /dev/null +++ b/src/modules/auth/hooks/useAuth.ts @@ -0,0 +1,82 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { authService } from "../api/auth.service"; +import { useApi } from "@/shared/api/hooks/use.api"; +import type { + LoginCredentials, + RegisterData, + OrganizationCreateData, +} from "../types/auth.types"; + +export const useAuth = () => { + const navigate = useNavigate(); + const { isLoading, error, request } = useApi(); + const [authError, setAuthError] = useState(null); + + const login = async (credentials: LoginCredentials) => { + setAuthError(null); + const result = await request(() => authService.login(credentials)); + + if (result) { + authService.saveAuthData(result.access_token, result.user); + + // Проверяем, есть ли уже организация + const orgs = await request(() => authService.getOrganizations()); + if (orgs && orgs.length > 0) { + authService.saveOrganization(orgs[0]); + navigate("/home"); + } else { + navigate("/create-organization"); + } + } else if (error) { + setAuthError(error); + } + }; + + const register = async (data: RegisterData) => { + setAuthError(null); + + if (data.password !== data.confirmPassword) { + setAuthError("Пароли не совпадают"); + return; + } + + const { confirmPassword, ...registerData } = data; + const result = await request(() => authService.register(registerData)); + + if (result) { + authService.saveAuthData(result.access_token, result.user); + navigate("/create-organization"); + } else if (error) { + setAuthError(error); + } + }; + + const createOrganization = async (data: OrganizationCreateData) => { + setAuthError(null); + const result = await request(() => authService.createOrganization(data)); + + if (result) { + authService.saveOrganization(result); + navigate("/home"); + } else if (error) { + setAuthError(error); + } + }; + + const logout = () => { + authService.logout(); + navigate("/"); + }; + + return { + login, + register, + createOrganization, + logout, + isLoading, + error: authError, + isAuthenticated: authService.isAuthenticated(), + hasOrganization: authService.hasOrganization(), + }; +}; diff --git a/src/modules/auth/types/auth.types.ts b/src/modules/auth/types/auth.types.ts new file mode 100644 index 0000000..713bdc1 --- /dev/null +++ b/src/modules/auth/types/auth.types.ts @@ -0,0 +1,38 @@ +export interface LoginCredentials { + username: string; + password: string; +} + +export interface RegisterData { + username: string; + email: string; + password: string; + confirmPassword: string; + first_name: string; + last_name: string; +} + +export interface AuthResponse { + access_token: string; + token_type: string; + user: { + id: number; + username: string; + email: string; + first_name: string; + last_name: string; + }; +} + +export interface OrganizationCreateData { + name: string; + logo?: File; +} + +export interface OrganizationResponse { + id: number; + name: string; + logo_url?: string; + created_at: string; + owner_id: number; +} diff --git a/src/pages/AuthPage.tsx b/src/pages/AuthPage.tsx new file mode 100644 index 0000000..418b2b3 --- /dev/null +++ b/src/pages/AuthPage.tsx @@ -0,0 +1,50 @@ +import { useState } from "react"; +import { ThemeToggle } from "@/modules/theme-changer/ui/Theme.toggle"; +import { LoginForm } from "@/modules/auth/components/LoginForm"; +import { RegisterForm } from "@/modules/auth/components/RegisterForm"; + +export const AuthPage = () => { + const [isLogin, setIsLogin] = useState(true); + + return ( +
+
+ +
+ +
+
+

+ IPS Manager +

+

+ {isLogin + ? "Войдите в систему для управления IPS агентами" + : "Создайте аккаунт для начала работы"} +

+
+ + {isLogin ? ( + setIsLogin(false)} /> + ) : ( + setIsLogin(true)} /> + )} +
+
+ ); +}; diff --git a/src/pages/CreateOrganizationPage.tsx b/src/pages/CreateOrganizationPage.tsx new file mode 100644 index 0000000..e94af62 --- /dev/null +++ b/src/pages/CreateOrganizationPage.tsx @@ -0,0 +1,52 @@ +import { ThemeToggle } from "@/modules/theme-changer/ui/Theme.toggle"; +import { CreateOrganizationForm } from "@/modules/auth/components/CreateOrganizationForm"; +import { useAuth } from "@/modules/auth/hooks/useAuth"; +import { Navigate } from "react-router-dom"; + +export const CreateOrganizationPage = () => { + const { isAuthenticated, hasOrganization } = useAuth(); + + if (!isAuthenticated) { + return ; + } + + if (hasOrganization) { + return ; + } + + return ( +
+
+ +
+ +
+
+

+ Создание организации +

+

+ Создайте организацию для начала работы с IPS платформой +

+
+ + +
+
+ ); +}; -- 2.52.0 From d348e0c34747b8e390ef97df649ef8fa083e1066 Mon Sep 17 00:00:00 2001 From: NikitaTorbenko <2015nekitciti@gmail.com> Date: Fri, 12 Jun 2026 19:25:23 +0300 Subject: [PATCH 07/15] feat: add plug and organization --- src/app/providers/routing.tsx | 9 + src/modules/auth/api/auth.service.ts | 338 ++++++++++++++++-- src/modules/auth/hooks/useAuth.ts | 2 +- src/modules/auth/types/auth.types.ts | 11 + .../components/OrganizationMembers.tsx | 307 ++++++++++++++++ .../organization/hooks/useOrganization.ts | 82 +++++ src/pages/OrganizationPage.tsx | 74 ++++ src/pages/home.page.tsx | 40 ++- 8 files changed, 832 insertions(+), 31 deletions(-) create mode 100644 src/modules/organization/components/OrganizationMembers.tsx create mode 100644 src/modules/organization/hooks/useOrganization.ts create mode 100644 src/pages/OrganizationPage.tsx diff --git a/src/app/providers/routing.tsx b/src/app/providers/routing.tsx index 7b1691e..0203765 100644 --- a/src/app/providers/routing.tsx +++ b/src/app/providers/routing.tsx @@ -4,6 +4,7 @@ import { HomePage } from "@/pages/home.page"; import { SecondaryPage } from "@/pages/secondary.page"; import { AuthPage } from "@/pages/AuthPage"; import { CreateOrganizationPage } from "@/pages/CreateOrganizationPage"; +import { OrganizationPage } from "@/pages/OrganizationPage"; import { ProtectedRoute } from "./helper/protected.route"; export const Routing = () => { @@ -29,6 +30,14 @@ export const Routing = () => { } /> + + + + } + /> { + const users = localStorage.getItem(MOCK_USERS_KEY); + if (!users) { + const defaultUsers = [ + { + id: 1, + username: "admin", + email: "admin@example.com", + password: "admin123", + first_name: "Admin", + last_name: "User", + }, + ]; + localStorage.setItem(MOCK_USERS_KEY, JSON.stringify(defaultUsers)); + return defaultUsers; + } + return JSON.parse(users); +}; + +const getMockOrganizations = () => { + const orgs = localStorage.getItem(MOCK_ORGS_KEY); + if (!orgs) { + return []; + } + return JSON.parse(orgs); +}; + +const getMockMembers = () => { + const members = localStorage.getItem(MOCK_MEMBERS_KEY); + if (!members) { + return []; + } + return JSON.parse(members); +}; + export const authService = { async login(credentials: LoginCredentials): Promise { - const formData = new URLSearchParams(); - formData.append("username", credentials.username); - formData.append("password", credentials.password); + return new Promise((resolve, reject) => { + setTimeout(() => { + const users = getMockUsers(); + const user = users.find( + (u: any) => + u.username === credentials.username && + u.password === credentials.password, + ); - return apiService.post("/auth/login", formData, { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, + if (user) { + resolve({ + access_token: `mock_token_${user.id}_${Date.now()}`, + token_type: "bearer", + user: { + id: user.id, + username: user.username, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + }, + }); + } else { + reject({ + response: { + data: { message: "Неверное имя пользователя или пароль" }, + }, + }); + } + }, 500); }); }, async register( data: Omit, ): Promise { - return apiService.post("/auth/register", { - username: data.username, - email: data.email, - password: data.password, - first_name: data.first_name, - last_name: data.last_name, + return new Promise((resolve, reject) => { + setTimeout(() => { + const users = getMockUsers(); + const existingUser = users.find( + (u: any) => u.username === data.username, + ); + + if (existingUser) { + reject({ + response: { + data: { message: "Пользователь с таким именем уже существует" }, + }, + }); + return; + } + + const existingEmail = users.find((u: any) => u.email === data.email); + if (existingEmail) { + reject({ + response: { + data: { message: "Пользователь с таким email уже существует" }, + }, + }); + return; + } + + const newUser = { + id: users.length + 1, + username: data.username, + email: data.email, + password: data.password, + first_name: data.first_name, + last_name: data.last_name, + }; + + users.push(newUser); + localStorage.setItem(MOCK_USERS_KEY, JSON.stringify(users)); + + resolve({ + access_token: `mock_token_${newUser.id}_${Date.now()}`, + token_type: "bearer", + user: { + id: newUser.id, + username: newUser.username, + email: newUser.email, + first_name: newUser.first_name, + last_name: newUser.last_name, + }, + }); + }, 500); }); }, async createOrganization( data: OrganizationCreateData, ): Promise { - const formData = new FormData(); - formData.append("name", data.name); - if (data.logo) { - formData.append("logo", data.logo); - } + return new Promise((resolve) => { + setTimeout(() => { + const orgs = getMockOrganizations(); + const token = this.getToken(); + const userId = token ? parseInt(token.split("_")[1]) : 1; - return apiService.post("/organizations", formData, { - headers: { - "Content-Type": "multipart/form-data", - }, + const newOrg = { + id: orgs.length + 1, + name: data.name, + logo_url: data.logo ? URL.createObjectURL(data.logo) : undefined, + created_at: new Date().toISOString(), + owner_id: userId, + }; + + orgs.push(newOrg); + localStorage.setItem(MOCK_ORGS_KEY, JSON.stringify(orgs)); + + // Добавляем владельца в члены организации + const members = getMockMembers(); + const newMember = { + id: members.length + 1, + organization_id: newOrg.id, + user_id: userId, + username: "temp_username", + email: "temp_email", + first_name: "temp_first", + last_name: "temp_last", + role: "owner", + joined_at: new Date().toISOString(), + }; + + // Получаем данные пользователя + const users = getMockUsers(); + const user = users.find((u: any) => u.id === userId); + if (user) { + newMember.username = user.username; + newMember.email = user.email; + newMember.first_name = user.first_name; + newMember.last_name = user.last_name; + } + + members.push(newMember); + localStorage.setItem(MOCK_MEMBERS_KEY, JSON.stringify(members)); + + resolve(newOrg); + }, 500); + }); + }, + + async getOrganizations(): Promise { + return new Promise((resolve) => { + setTimeout(() => { + const orgs = getMockOrganizations(); + resolve(orgs); + }, 300); + }); + }, + + async getOrganizationMembers( + organizationId: number, + ): Promise { + return new Promise((resolve) => { + setTimeout(() => { + const members = getMockMembers(); + const orgMembers = members.filter( + (m: any) => m.organization_id === organizationId, + ); + resolve(orgMembers); + }, 300); + }); + }, + + async updateMemberRole( + organizationId: number, + userId: number, + newRole: string, + ): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => { + const members = getMockMembers(); + const memberIndex = members.findIndex( + (m: any) => + m.organization_id === organizationId && m.user_id === userId, + ); + + if (memberIndex !== -1) { + members[memberIndex].role = newRole; + localStorage.setItem(MOCK_MEMBERS_KEY, JSON.stringify(members)); + resolve(); + } else { + reject({ response: { data: { message: "Участник не найден" } } }); + } + }, 500); + }); + }, + + async removeMember(organizationId: number, userId: number): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => { + const members = getMockMembers(); + const filteredMembers = members.filter( + (m: any) => + !(m.organization_id === organizationId && m.user_id === userId), + ); + + if (filteredMembers.length !== members.length) { + localStorage.setItem( + MOCK_MEMBERS_KEY, + JSON.stringify(filteredMembers), + ); + resolve(); + } else { + reject({ response: { data: { message: "Участник не найден" } } }); + } + }, 500); + }); + }, + + async addMember( + organizationId: number, + email: string, + role: string, + ): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => { + const users = getMockUsers(); + const user = users.find((u: any) => u.email === email); + + if (!user) { + reject({ + response: { + data: { message: "Пользователь с таким email не найден" }, + }, + }); + return; + } + + const members = getMockMembers(); + const existingMember = members.find( + (m: any) => + m.organization_id === organizationId && m.user_id === user.id, + ); + + if (existingMember) { + reject({ + response: { + data: { + message: "Пользователь уже является участником организации", + }, + }, + }); + return; + } + + const newMember = { + id: members.length + 1, + organization_id: organizationId, + user_id: user.id, + username: user.username, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + role: role, + joined_at: new Date().toISOString(), + }; + + members.push(newMember); + localStorage.setItem(MOCK_MEMBERS_KEY, JSON.stringify(members)); + + resolve(newMember); + }, 500); }); }, async getCurrentUser(): Promise { - return apiService.get("/users/me"); - }, + return new Promise((resolve, reject) => { + const token = this.getToken(); + if (!token) { + reject({ response: { data: { message: "Не авторизован" } } }); + return; + } - async getOrganizations(): Promise { - return apiService.get("/organizations"); + const userId = parseInt(token.split("_")[1]); + const users = getMockUsers(); + const user = users.find((u: any) => u.id === userId); + + if (user) { + resolve({ + id: user.id, + username: user.username, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + }); + } else { + reject({ response: { data: { message: "Пользователь не найден" } } }); + } + }); }, logout(): void { diff --git a/src/modules/auth/hooks/useAuth.ts b/src/modules/auth/hooks/useAuth.ts index 3d72584..a8c5df4 100644 --- a/src/modules/auth/hooks/useAuth.ts +++ b/src/modules/auth/hooks/useAuth.ts @@ -6,6 +6,7 @@ import type { LoginCredentials, RegisterData, OrganizationCreateData, + OrganizationMember, } from "../types/auth.types"; export const useAuth = () => { @@ -20,7 +21,6 @@ export const useAuth = () => { if (result) { authService.saveAuthData(result.access_token, result.user); - // Проверяем, есть ли уже организация const orgs = await request(() => authService.getOrganizations()); if (orgs && orgs.length > 0) { authService.saveOrganization(orgs[0]); diff --git a/src/modules/auth/types/auth.types.ts b/src/modules/auth/types/auth.types.ts index 713bdc1..3bf3e6d 100644 --- a/src/modules/auth/types/auth.types.ts +++ b/src/modules/auth/types/auth.types.ts @@ -36,3 +36,14 @@ export interface OrganizationResponse { created_at: string; owner_id: number; } + +export interface OrganizationMember { + id: number; + user_id: number; + username: string; + email: string; + first_name: string; + last_name: string; + role: "owner" | "admin" | "member"; + joined_at: string; +} diff --git a/src/modules/organization/components/OrganizationMembers.tsx b/src/modules/organization/components/OrganizationMembers.tsx new file mode 100644 index 0000000..65d5983 --- /dev/null +++ b/src/modules/organization/components/OrganizationMembers.tsx @@ -0,0 +1,307 @@ +import React, { useState } from "react"; +import { useOrganization } from "../hooks/useOrganization"; + +export const OrganizationMembers = () => { + const { members, isLoading, updateMemberRole, removeMember, addMember } = + useOrganization(); + const [showAddModal, setShowAddModal] = useState(false); + const [newMemberEmail, setNewMemberEmail] = useState(""); + const [newMemberRole, setNewMemberRole] = useState<"admin" | "member">( + "member", + ); + const [actionLoading, setActionLoading] = useState(null); + + const handleRoleChange = async (userId: number, newRole: string) => { + setActionLoading(userId); + await updateMemberRole(userId, newRole); + setActionLoading(null); + }; + + const handleRemoveMember = async (userId: number, memberName: string) => { + if ( + confirm( + `Вы уверены, что хотите удалить пользователя "${memberName}" из организации?`, + ) + ) { + setActionLoading(userId); + await removeMember(userId); + setActionLoading(null); + } + }; + + const handleAddMember = async (e: React.FormEvent) => { + e.preventDefault(); + const success = await addMember(newMemberEmail, newMemberRole); + if (success) { + setShowAddModal(false); + setNewMemberEmail(""); + setNewMemberRole("member"); + } + }; + + const getRoleLabel = (role: string) => { + switch (role) { + case "owner": + return "Владелец"; + case "admin": + return "Администратор"; + case "member": + return "Участник"; + default: + return role; + } + }; + + const getRoleColor = (role: string) => { + switch (role) { + case "owner": + return "text-yellow-600 dark:text-yellow-400"; + case "admin": + return "text-blue-600 dark:text-blue-400"; + case "member": + return "text-green-600 dark:text-green-400"; + default: + return ""; + } + }; + + if (isLoading && members.length === 0) { + return ( +
+
+ Загрузка участников... +
+
+ ); + } + + return ( +
+
+

+ Участники организации +

+ +
+ +
+ + + + + + + + + + + {members.map((member) => ( + + + + + + + ))} + +
+ Имя + + Email + + Роль + + Действия +
+ {member.first_name} {member.last_name} +
+ @{member.username} +
+
+ {member.email} + + {member.role === "owner" ? ( + + {getRoleLabel(member.role)} + + ) : ( + + )} + + {member.role !== "owner" && ( + + )} +
+
+ + {members.length === 0 && !isLoading && ( +
+ В организации пока нет участников +
+ )} + + {showAddModal && ( +
+
+

+ Добавить участника +

+
+
+ + setNewMemberEmail(e.target.value)} + className="w-full px-4 py-2 rounded-lg border focus:outline-none focus:ring-2" + style={{ + backgroundColor: "var(--input-bg)", + color: "var(--text-primary)", + borderColor: "var(--border)", + "--tw-ring-color": "var(--accent)", + }} + required + placeholder="user@example.com" + /> +
+
+ + +
+
+ + +
+
+
+
+ )} +
+ ); +}; diff --git a/src/modules/organization/hooks/useOrganization.ts b/src/modules/organization/hooks/useOrganization.ts new file mode 100644 index 0000000..46478e3 --- /dev/null +++ b/src/modules/organization/hooks/useOrganization.ts @@ -0,0 +1,82 @@ +import { useState, useEffect } from "react"; +import { authService } from "@/modules/auth/api/auth.service"; +import { useApi } from "@/shared/api/hooks/use.api"; +import type { OrganizationMember } from "@/modules/auth/types/auth.types"; + +export const useOrganization = () => { + const { isLoading, error, request } = useApi(); + const [members, setMembers] = useState([]); + const [organization, setOrganization] = useState( + authService.getCurrentOrganization(), + ); + + const loadMembers = async () => { + if (organization) { + const result = await request(() => + authService.getOrganizationMembers(organization.id), + ); + if (result) { + setMembers(result); + } + } + }; + + useEffect(() => { + loadMembers(); + }, [organization]); + + const updateMemberRole = async (userId: number, newRole: string) => { + if (organization) { + const result = await request(() => + authService.updateMemberRole(organization.id, userId, newRole), + ); + if (result !== undefined) { + await loadMembers(); + return true; + } + } + return false; + }; + + const removeMember = async (userId: number) => { + if (organization) { + const result = await request(() => + authService.removeMember(organization.id, userId), + ); + if (result !== undefined) { + await loadMembers(); + return true; + } + } + return false; + }; + + const addMember = async (email: string, role: string) => { + if (organization) { + const result = await request(() => + authService.addMember(organization.id, email, role), + ); + if (result) { + await loadMembers(); + return true; + } + } + return false; + }; + + const refreshOrganization = () => { + setOrganization(authService.getCurrentOrganization()); + }; + + return { + members, + organization, + isLoading, + error, + updateMemberRole, + removeMember, + addMember, + refreshOrganization, + loadMembers, + }; +}; diff --git a/src/pages/OrganizationPage.tsx b/src/pages/OrganizationPage.tsx new file mode 100644 index 0000000..35fee42 --- /dev/null +++ b/src/pages/OrganizationPage.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { ThemeToggle } from "@/modules/theme-changer/ui/Theme.toggle"; +import { OrganizationMembers } from "@/modules/organization/components/OrganizationMembers"; +import { useAuth } from "@/modules/auth/hooks/useAuth"; +import { Navigate, useNavigate } from "react-router-dom"; +import { authService } from "@/modules/auth/api/auth.service"; + +export const OrganizationPage = () => { + const { isAuthenticated, hasOrganization } = useAuth(); + const navigate = useNavigate(); + const organization = authService.getCurrentOrganization(); + + if (!isAuthenticated) { + return ; + } + + if (!hasOrganization) { + return ; + } + + return ( +
+
+ +
+ +
+
+ + +
+ {organization?.logo_url && ( + {organization.name} + )} +
+

+ {organization?.name} +

+

+ Управление участниками организации +

+
+
+
+ +
+ +
+
+
+ ); +}; diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index df87d99..90ab78e 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -1,13 +1,47 @@ import { ThemeToggle } from "@/modules/theme-changer/ui/Theme.toggle"; +import { useNavigate } from "react-router-dom"; +import { useAuth } from "@/modules/auth/hooks/useAuth"; export const HomePage = () => { + const navigate = useNavigate(); + const { logout } = useAuth(); + return (
- - Домашняя +
+ +
+ + +
+
+ +
+

+ Добро пожаловать в IPS Manager +

+
); }; -- 2.52.0 From ca6b5f40c3471035973fda589f2909ba4a7a98f8 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Sat, 13 Jun 2026 20:14:29 +0300 Subject: [PATCH 08/15] chore: add dockerfile, nginx.conf and infra/docker-compose.yml --- dockerfile | 16 ++++++++++++++++ infra/docker-compose.yml | 7 +++++++ nginx.conf | 31 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 dockerfile create mode 100644 infra/docker-compose.yml create mode 100644 nginx.conf diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..0e50b89 --- /dev/null +++ b/dockerfile @@ -0,0 +1,16 @@ +FROM node:25-alpine3.23 AS builder + +WORKDIR /app + +COPY package.json yarn.lock ./ + +RUN yarn install --frozen-lockfile +COPY . . +RUN yarn build +FROM nginx:alpine +COPY --from=builder /app/dist /usr/share/nginx/html + +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml new file mode 100644 index 0000000..331a32d --- /dev/null +++ b/infra/docker-compose.yml @@ -0,0 +1,7 @@ +services: + app: + build: + context: .. + dockerfile: dockerfile + ports: + - "80:80" diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..66def58 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,31 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + location /api/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } + + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Gzip сжатие + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; +} -- 2.52.0 From 0f73ca72d76c595ab68ee81526a333bcef5c8ae0 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Sat, 13 Jun 2026 20:56:04 +0300 Subject: [PATCH 09/15] feat: container registry deploy (build in CI, pull on server) --- .gitea/workflows/cd.yml | 22 ++++++++++++++++++ .gitea/workflows/ci.yml | 27 ++++++++++++++++++++++ .gitea/workflows/deploy.yml | 36 ++++++++++++++++++++++++++++++ infra/ansible/ansible.cfg | 7 ++++++ infra/ansible/inventory/hosts.yml | 10 +++++++++ infra/ansible/playbook.yml | 36 ++++++++++++++++++++++++++++++ infra/ansible/playbooks/deploy.yml | 0 infra/ansible/requirements.yml | 4 ++++ infra/docker-compose.yml | 4 +--- 9 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 .gitea/workflows/cd.yml create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/deploy.yml create mode 100644 infra/ansible/ansible.cfg create mode 100644 infra/ansible/inventory/hosts.yml create mode 100644 infra/ansible/playbook.yml create mode 100644 infra/ansible/playbooks/deploy.yml create mode 100644 infra/ansible/requirements.yml diff --git a/.gitea/workflows/cd.yml b/.gitea/workflows/cd.yml new file mode 100644 index 0000000..0adf334 --- /dev/null +++ b/.gitea/workflows/cd.yml @@ -0,0 +1,22 @@ +name: Deploy + +on: + push: + branches: master + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Ansible + run: | + pip install -r infra/ansible/requirements.txt + ansible-galaxy install -r infra/ansible/requirements.yml + + - name: Run playbook + run: | + echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > .vault_pass + ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbook.yml --vault-password-file .vault_pass + rm .vault_pass diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..f8d589e --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: + push: + branches: + - dev + pull_request: + branches: + - master + + + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Node setup + uses: actions/setup-node@v6 + with: + node-version: "24.12" + + - name: Install deps + run: npm install + - name: Lint + run: npm run build diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..b0d08d3 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,36 @@ +name: Deploy + +on: + push: + branches: [dev] + +env: + REGISTRY: gitea.d3m0k1d.ru + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Ansible + run: apt update && apt install -y ansible + + - name: Login to registry + run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login $REGISTRY -u "${{ secrets.REGISTRY_USER }}" --password-stdin + + - name: Build and push image + run: | + IMAGE=$REGISTRY/hellreign/frontend + docker build -f dockerfile -t $IMAGE:dev -t $IMAGE:${{ gitea.sha }} . + docker push $IMAGE:dev + docker push $IMAGE:${{ gitea.sha }} + + - name: Deploy via Ansible + run: | + echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > .vault_pass + ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbook.yml \ + --vault-password-file .vault_pass \ + -e registry=$REGISTRY \ + -e tag=${{ gitea.sha }} + rm .vault_pass diff --git a/infra/ansible/ansible.cfg b/infra/ansible/ansible.cfg new file mode 100644 index 0000000..20f1d45 --- /dev/null +++ b/infra/ansible/ansible.cfg @@ -0,0 +1,7 @@ +[defaults] +inventory = inventory/hosts.yml +host_key_checking = False +remote_user = root +private_key_file = ~/.ssh/id_rsa +interpreter_python = /usr/bin/python3 +stdout_callback = yaml diff --git a/infra/ansible/inventory/hosts.yml b/infra/ansible/inventory/hosts.yml new file mode 100644 index 0000000..9b91618 --- /dev/null +++ b/infra/ansible/inventory/hosts.yml @@ -0,0 +1,10 @@ +all: + hosts: + prod: + ansible_host: + ansible_user: root + ansible_port: 22 + vars: + registry: gitea.d3m0k1d.ru + registry_user: + registry_password: diff --git a/infra/ansible/playbook.yml b/infra/ansible/playbook.yml new file mode 100644 index 0000000..283f132 --- /dev/null +++ b/infra/ansible/playbook.yml @@ -0,0 +1,36 @@ +--- +- name: Deploy Frontend + hosts: prod + vars: + deploy_path: /opt/app + + tasks: + - name: Ensure deployment directory + ansible.builtin.file: + path: "{{ deploy_path }}" + state: directory + + - name: Copy docker-compose + ansible.builtin.copy: + src: "{{ playbook_dir }}/../docker-compose.yml" + dest: "{{ deploy_path }}/docker-compose.yml" + + - name: Login to registry + ansible.builtin.shell: + cmd: echo "{{ registry_password }}" | docker login "{{ registry }}" -u "{{ registry_user }}" --password-stdin + + - name: Pull images + ansible.builtin.shell: + cmd: docker compose pull + chdir: "{{ deploy_path }}" + environment: + REGISTRY: "{{ registry }}" + TAG: "{{ tag }}" + + - name: Restart services + ansible.builtin.shell: + cmd: docker compose up -d --remove-orphans + chdir: "{{ deploy_path }}" + environment: + REGISTRY: "{{ registry }}" + TAG: "{{ tag }}" diff --git a/infra/ansible/playbooks/deploy.yml b/infra/ansible/playbooks/deploy.yml new file mode 100644 index 0000000..e69de29 diff --git a/infra/ansible/requirements.yml b/infra/ansible/requirements.yml new file mode 100644 index 0000000..9bb092f --- /dev/null +++ b/infra/ansible/requirements.yml @@ -0,0 +1,4 @@ +--- +roles: [] + +collections: [] diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 331a32d..5530f18 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -1,7 +1,5 @@ services: app: - build: - context: .. - dockerfile: dockerfile + image: ${REGISTRY}/hellreign/frontend:${TAG} ports: - "80:80" -- 2.52.0 From fb6bf6e1bf4b52495674dc2185d2b23aa8df29ec Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Sat, 13 Jun 2026 20:56:31 +0300 Subject: [PATCH 10/15] fix: remove stale files, fix ci node version --- .gitea/workflows/cd.yml | 22 ---------------------- .gitea/workflows/ci.yml | 4 ++-- infra/ansible/playbooks/deploy.yml | 0 3 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 .gitea/workflows/cd.yml delete mode 100644 infra/ansible/playbooks/deploy.yml diff --git a/.gitea/workflows/cd.yml b/.gitea/workflows/cd.yml deleted file mode 100644 index 0adf334..0000000 --- a/.gitea/workflows/cd.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Deploy - -on: - push: - branches: master - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Install Ansible - run: | - pip install -r infra/ansible/requirements.txt - ansible-galaxy install -r infra/ansible/requirements.yml - - - name: Run playbook - run: | - echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > .vault_pass - ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbook.yml --vault-password-file .vault_pass - rm .vault_pass diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index f8d589e..42f8452 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -19,9 +19,9 @@ jobs: - name: Node setup uses: actions/setup-node@v6 with: - node-version: "24.12" + node-version: "25" - name: Install deps run: npm install - - name: Lint + - name: Build run: npm run build diff --git a/infra/ansible/playbooks/deploy.yml b/infra/ansible/playbooks/deploy.yml deleted file mode 100644 index e69de29..0000000 -- 2.52.0 From 2a108e1b5ad4c793a9a88cfb9a97cf4bf05546d7 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Sat, 13 Jun 2026 21:00:09 +0300 Subject: [PATCH 11/15] feat: ci for all pushes, deploy with ssh key, local compose with build --- .gitea/workflows/ci.yml | 20 +++++++++----------- .gitea/workflows/deploy.yml | 16 +++++++++++----- docker-compose.yml | 7 +++++++ infra/ansible/inventory/hosts.yml | 4 ---- infra/ansible/playbook.yml | 6 +----- 5 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 docker-compose.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 42f8452..02b16f4 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,27 +1,25 @@ -name: ci +name: CI on: push: - branches: - - dev - pull_request: - branches: + branches-ignore: - master - - jobs: build: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Node setup - uses: actions/setup-node@v6 + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 with: node-version: "25" - name: Install deps run: npm install + + - name: Lint + run: npm run lint + - name: Build run: npm run build diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index b0d08d3..ea2cbd2 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -13,24 +13,30 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + - name: Install Ansible run: apt update && apt install -y ansible - name: Login to registry run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login $REGISTRY -u "${{ secrets.REGISTRY_USER }}" --password-stdin - - name: Build and push image + - name: Build and push run: | IMAGE=$REGISTRY/hellreign/frontend - docker build -f dockerfile -t $IMAGE:dev -t $IMAGE:${{ gitea.sha }} . + docker build -f dockerfile -t $IMAGE:dev -t $IMAGE:latest . docker push $IMAGE:dev - docker push $IMAGE:${{ gitea.sha }} + docker push $IMAGE:latest - - name: Deploy via Ansible + - name: Deploy run: | echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > .vault_pass ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbook.yml \ --vault-password-file .vault_pass \ -e registry=$REGISTRY \ - -e tag=${{ gitea.sha }} + -e tag=latest rm .vault_pass diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..768f283 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +services: + app: + build: + context: . + dockerfile: dockerfile + ports: + - "80:80" diff --git a/infra/ansible/inventory/hosts.yml b/infra/ansible/inventory/hosts.yml index 9b91618..acea417 100644 --- a/infra/ansible/inventory/hosts.yml +++ b/infra/ansible/inventory/hosts.yml @@ -4,7 +4,3 @@ all: ansible_host: ansible_user: root ansible_port: 22 - vars: - registry: gitea.d3m0k1d.ru - registry_user: - registry_password: diff --git a/infra/ansible/playbook.yml b/infra/ansible/playbook.yml index 283f132..5468699 100644 --- a/infra/ansible/playbook.yml +++ b/infra/ansible/playbook.yml @@ -15,10 +15,6 @@ src: "{{ playbook_dir }}/../docker-compose.yml" dest: "{{ deploy_path }}/docker-compose.yml" - - name: Login to registry - ansible.builtin.shell: - cmd: echo "{{ registry_password }}" | docker login "{{ registry }}" -u "{{ registry_user }}" --password-stdin - - name: Pull images ansible.builtin.shell: cmd: docker compose pull @@ -27,7 +23,7 @@ REGISTRY: "{{ registry }}" TAG: "{{ tag }}" - - name: Restart services + - name: Start services ansible.builtin.shell: cmd: docker compose up -d --remove-orphans chdir: "{{ deploy_path }}" -- 2.52.0 From 51c708bf47ee0050ab4dbd967058ac07e883b22e Mon Sep 17 00:00:00 2001 From: NikitaTorbenko <2015nekitciti@gmail.com> Date: Sat, 13 Jun 2026 22:21:23 +0300 Subject: [PATCH 12/15] feat: create home page --- package.json | 1 + src/pages/home.page.tsx | 553 +++++++++++++++++++++++++++++++++++++--- yarn.lock | 5 + 3 files changed, 528 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index d4442d5..e4f5c7e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@tailwindcss/vite": "^4.3.0", "axios": "^1.17.0", + "lucide-react": "^1.18.0", "react": "^19.2.6", "react-dom": "^19.2.6", "react-icons": "^5.6.0", diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 90ab78e..1d253de 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -1,47 +1,538 @@ import { ThemeToggle } from "@/modules/theme-changer/ui/Theme.toggle"; import { useNavigate } from "react-router-dom"; import { useAuth } from "@/modules/auth/hooks/useAuth"; +import { authService } from "@/modules/auth/api/auth.service"; +import { + Shield, + Users, + Bot, + Settings, + Activity, + LogOut, + ChevronRight, + Zap, + Lock, + Cloud, + TrendingUp, + Bell, + Server, + Sparkles, + AlertCircle, + CheckCircle, + Clock, +} from "lucide-react"; export const HomePage = () => { const navigate = useNavigate(); const { logout } = useAuth(); + const organization = authService.getCurrentOrganization(); + const authStorage = localStorage.getItem("auth-storage"); + const user = authStorage ? JSON.parse(authStorage).state?.user : null; + + const stats = [ + { label: "Активные агенты", value: "12", change: "+2", icon: Server }, + { label: "Заблокировано IP", value: "1,284", change: "+128", icon: Shield }, + { label: "Активных правил", value: "47", change: "+5", icon: Lock }, + { + label: "Атак предотвращено", + value: "3,721", + change: "+342", + icon: TrendingUp, + }, + ]; + + const recentActivities = [ + { + id: 1, + type: "attack", + message: "Обнаружена brute-force атака с IP 192.168.1.45", + time: "2 минуты назад", + severity: "high", + }, + { + id: 2, + type: "rule", + message: "Новое правило фильтрации добавлено администратором", + time: "15 минут назад", + severity: "info", + }, + { + id: 3, + type: "agent", + message: "Агент на сервере web-01 успешно обновлен", + time: "1 час назад", + severity: "success", + }, + { + id: 4, + type: "ai", + message: "AI предложил новые правила для обнаружения ботов", + time: "3 часа назад", + severity: "info", + }, + ]; + + const quickActions = [ + { + name: "IPS Агенты", + icon: Server, + path: "/agents", + description: "Управление агентами", + color: "#6366f1", + }, + { + name: "Правила", + icon: Shield, + path: "/rules", + description: "Правила фильтрации", + color: "#22c55e", + }, + { + name: "AI Аналитика", + icon: Bot, + path: "/ai-analytics", + description: "Анализ логов", + color: "#8b5cf6", + }, + { + name: "Команда", + icon: Users, + path: "/organization", + description: "Управление доступом", + color: "#f59e0b", + }, + ]; + + const getSeverityStyles = (severity: string) => { + switch (severity) { + case "high": + return { + bg: "#ef444410", + border: "#ef4444", + icon: AlertCircle, + text: "#ef4444", + }; + case "success": + return { + bg: "#22c55e10", + border: "#22c55e", + icon: CheckCircle, + text: "#22c55e", + }; + default: + return { + bg: "#6366f110", + border: "#6366f1", + icon: Sparkles, + text: "#6366f1", + }; + } + }; return (
-
- -
- - -
-
+ {/* Header */} +
+
+
+ {/* Logo */} +
+
+ +
+
+

+ IPS Manager +

+ {organization && ( +

+ {organization.name} +

+ )} +
+
-
-

- Добро пожаловать в IPS Manager -

-
+ {/* Right section */} +
+ + +
+
+ {user?.first_name?.[0]} + {user?.last_name?.[0]} +
+
+

+ {user?.first_name} {user?.last_name} +

+

+ Администратор +

+
+
+ +
+
+
+
+ +
+ {/* Welcome Section */} +
+
+
+
+

+ Добро пожаловать, {user?.first_name || "Администратор"}! +

+

+ Система IPS мониторинга и защиты +

+
+ + + Все системы работают стабильно + +
+
+
+ +
+
+
+
+ + {/* Stats Grid */} +
+ {stats.map((stat, index) => { + const Icon = stat.icon; + return ( +
+
+
+ +
+ + {stat.change} + +
+

+ {stat.value} +

+

+ {stat.label} +

+
+ ); + })} +
+ +
+ {/* Quick Actions */} +
+
+

+ Быстрый доступ +

+
+ {quickActions.map((action, index) => { + const Icon = action.icon; + return ( + + ); + })} +
+
+
+ + {/* Recent Activity */} +
+
+

+ Последние события +

+ +
+
+ {recentActivities.map((activity) => { + const severityStyles = getSeverityStyles(activity.severity); + const Icon = severityStyles.icon; + return ( +
+
+
+ +
+
+

+ {activity.message} +

+
+ +

+ {activity.time} +

+
+
+
+
+ ); + })} +
+ +
+
+ + {/* AI Insights */} +
+
+
+
+ +
+
+

+ AI Аналитика +

+

+ Новые предложения на основе анализа логов +

+
+
+ +
+
+
+

+ 🔍 Обнаружена аномалия +

+

+ Повышенная активность с IP-адресов из диапазона 185.xxx.xx.xx +

+
+
+

+ 💡 Предложено правил +

+

+ AI предлагает 3 новых правила для блокировки бот-сканеров +

+
+
+

+ ⚡ Оптимизация +

+

+ Рекомендуется обновить 5 существующих правил для повышения + эффективности +

+
+
+
+
); }; diff --git a/yarn.lock b/yarn.lock index 408b0ad..3106b18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1284,6 +1284,11 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lucide-react@^1.18.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-1.18.0.tgz#d1b8754d9c427061261c6a6a0b7fa6ecda36c84f" + integrity sha512-LZDb7H/0YfM+RJncD0hDQRCAu+vSGODqpe35TuVI8EuXaRjkczbsx7p8dY4J87F/MUSj6bpYqeI8nw8qXaAdmA== + magic-string@^0.30.21: version "0.30.21" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" -- 2.52.0 From e481c8b3a00e2270c22b39176294d8c5a0081fe0 Mon Sep 17 00:00:00 2001 From: NikitaTorbenko <2015nekitciti@gmail.com> Date: Sat, 13 Jun 2026 22:32:53 +0300 Subject: [PATCH 13/15] fix package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4f5c7e..501b254 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc -b && vite build", + "build": "vite build", "lint": "eslint .", "preview": "vite preview" }, -- 2.52.0 From 6367cdae56cb145a959fb55ab37236f1af301684 Mon Sep 17 00:00:00 2001 From: NikitaTorbenko <2015nekitciti@gmail.com> Date: Sat, 13 Jun 2026 22:39:30 +0300 Subject: [PATCH 14/15] fix: home --- src/app/providers/helper/protected.route.tsx | 2 +- src/app/providers/routing.tsx | 9 +- src/modules/auth/hooks/useAuth.ts | 5 +- src/pages/AuthPage.tsx | 17 +- src/pages/home.page.tsx | 825 ++++++++++++------- 5 files changed, 526 insertions(+), 332 deletions(-) diff --git a/src/app/providers/helper/protected.route.tsx b/src/app/providers/helper/protected.route.tsx index f803ad1..b702b45 100644 --- a/src/app/providers/helper/protected.route.tsx +++ b/src/app/providers/helper/protected.route.tsx @@ -8,7 +8,7 @@ interface ProtectedRouteProps { export const ProtectedRoute: React.FC = ({ children, - fallbackPath = "/", + fallbackPath = "/auth", }) => { const isAuthenticated = authService.isAuthenticated(); diff --git a/src/app/providers/routing.tsx b/src/app/providers/routing.tsx index 0203765..132830c 100644 --- a/src/app/providers/routing.tsx +++ b/src/app/providers/routing.tsx @@ -17,16 +17,13 @@ export const Routing = () => { } > - } /> + } /> + } /> } - /> - - + } /> diff --git a/src/modules/auth/hooks/useAuth.ts b/src/modules/auth/hooks/useAuth.ts index a8c5df4..913f55a 100644 --- a/src/modules/auth/hooks/useAuth.ts +++ b/src/modules/auth/hooks/useAuth.ts @@ -6,7 +6,6 @@ import type { LoginCredentials, RegisterData, OrganizationCreateData, - OrganizationMember, } from "../types/auth.types"; export const useAuth = () => { @@ -24,7 +23,7 @@ export const useAuth = () => { const orgs = await request(() => authService.getOrganizations()); if (orgs && orgs.length > 0) { authService.saveOrganization(orgs[0]); - navigate("/home"); + navigate("/"); } else { navigate("/create-organization"); } @@ -58,7 +57,7 @@ export const useAuth = () => { if (result) { authService.saveOrganization(result); - navigate("/home"); + navigate("/"); } else if (error) { setAuthError(error); } diff --git a/src/pages/AuthPage.tsx b/src/pages/AuthPage.tsx index 418b2b3..ca2cd28 100644 --- a/src/pages/AuthPage.tsx +++ b/src/pages/AuthPage.tsx @@ -1,17 +1,30 @@ -import { useState } from "react"; +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; import { ThemeToggle } from "@/modules/theme-changer/ui/Theme.toggle"; import { LoginForm } from "@/modules/auth/components/LoginForm"; import { RegisterForm } from "@/modules/auth/components/RegisterForm"; export const AuthPage = () => { const [isLogin, setIsLogin] = useState(true); + const navigate = useNavigate(); return (
-
+
+
diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 1d253de..334f209 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -6,7 +6,6 @@ import { Shield, Users, Bot, - Settings, Activity, LogOut, ChevronRight, @@ -20,11 +19,14 @@ import { AlertCircle, CheckCircle, Clock, + LogIn, + UserPlus, } from "lucide-react"; export const HomePage = () => { const navigate = useNavigate(); const { logout } = useAuth(); + const isAuthenticated = authService.isAuthenticated(); const organization = authService.getCurrentOrganization(); const authStorage = localStorage.getItem("auth-storage"); const user = authStorage ? JSON.parse(authStorage).state?.user : null; @@ -72,37 +74,66 @@ export const HomePage = () => { }, ]; - const quickActions = [ + const features = [ { - name: "IPS Агенты", + title: "Централизованное управление", + description: "Управляйте всеми IPS агентами из единого интерфейса", icon: Server, - path: "/agents", - description: "Управление агентами", color: "#6366f1", }, { - name: "Правила", - icon: Shield, - path: "/rules", - description: "Правила фильтрации", - color: "#22c55e", - }, - { - name: "AI Аналитика", + title: "AI Аналитика", + description: "Искусственный интеллект помогает находить сложные атаки", icon: Bot, - path: "/ai-analytics", - description: "Анализ логов", color: "#8b5cf6", }, { - name: "Команда", - icon: Users, - path: "/organization", - description: "Управление доступом", + title: "Мгновенная блокировка", + description: "Автоматическая блокировка IP при обнаружении угроз", + icon: Zap, color: "#f59e0b", }, + { + title: "Безопасность данных", + description: "LLM не получает доступ к чувствительным логам", + icon: Lock, + color: "#22c55e", + }, ]; + const quickActions = isAuthenticated + ? [ + { + name: "IPS Агенты", + icon: Server, + path: "/agents", + description: "Управление агентами", + color: "#6366f1", + }, + { + name: "Правила", + icon: Shield, + path: "/rules", + description: "Правила фильтрации", + color: "#22c55e", + }, + { + name: "AI Аналитика", + icon: Bot, + path: "/ai-analytics", + description: "Анализ логов", + color: "#8b5cf6", + }, + { + name: "Команда", + icon: Users, + path: "/organization", + description: "Управление доступом", + color: "#f59e0b", + }, + ] + : []; + const getSeverityStyles = (severity: string) => { switch (severity) { case "high": @@ -169,54 +200,88 @@ export const HomePage = () => { {/* Right section */}
- - -
-
- {user?.first_name?.[0]} - {user?.last_name?.[0]} -
-
-

+

-
- + + + + +
+
+ {user?.first_name?.[0]} + {user?.last_name?.[0]} +
+
+

+ {user?.first_name} {user?.last_name} +

+

+ Администратор +

+
+
+ + + ) : ( + <> + + + + + )}
- {/* Welcome Section */} + {/* Hero Section */}
{

- Добро пожаловать, {user?.first_name || "Администратор"}! + {isAuthenticated + ? `Добро пожаловать, ${user?.first_name || "Администратор"}!` + : "Добро пожаловать в IPS Manager"}

- Система IPS мониторинга и защиты + {isAuthenticated + ? "Система IPS мониторинга и защиты" + : "Современная платформа для централизованного управления IPS агентами"}

-
- - - Все системы работают стабильно - -
+ {!isAuthenticated && ( +
+ + +
+ )} + {isAuthenticated && ( +
+ + + Все системы работают стабильно + +
+ )}
@@ -246,292 +330,393 @@ export const HomePage = () => {
- {/* Stats Grid */} -
- {stats.map((stat, index) => { - const Icon = stat.icon; - return ( + {/* Stats Grid - только для авторизованных */} + {isAuthenticated && ( +
+ {stats.map((stat, index) => { + const Icon = stat.icon; + return ( +
+
+
+ +
+ + {stat.change} + +
+

+ {stat.value} +

+

+ {stat.label} +

+
+ ); + })} +
+ )} + + {/* Features Section - для всех */} +
+
+

+ Ключевые возможности +

+

+ Всё необходимое для защиты вашей инфраструктуры +

+
+
+ {features.map((feature, index) => { + const Icon = feature.icon; + return ( +
+
+ +
+

+ {feature.title} +

+

+ {feature.description} +

+
+ ); + })} +
+
+ + {isAuthenticated ? ( + <> +
+ {/* Quick Actions */} +
+
+

+ Быстрый доступ +

+
+ {quickActions.map((action, index) => { + const Icon = action.icon; + return ( + + ); + })} +
+
+
+ + {/* Recent Activity */}
-
-
+

- -

- - {stat.change} - + Последние события + +
-

+ {recentActivities.map((activity) => { + const severityStyles = getSeverityStyles(activity.severity); + const Icon = severityStyles.icon; + return ( +
+
+
+ +
+
+

+ {activity.message} +

+
+ +

+ {activity.time} +

+
+
+
+
+ ); + })} +

+
- ); - })} -
+
-
- {/* Quick Actions */} -
+ {/* AI Insights */}
-

- Быстрый доступ -

-
- {quickActions.map((action, index) => { - const Icon = action.icon; - return ( - - ); - })} + AI Аналитика + +

+ Новые предложения на основе анализа логов +

+
+
+ +
+
+
+

+ 🔍 Обнаружена аномалия +

+

+ Повышенная активность с IP-адресов из диапазона + 185.xxx.xx.xx +

+
+
+

+ 💡 Предложено правил +

+

+ AI предлагает 3 новых правила для блокировки бот-сканеров +

+
+
+

+ ⚡ Оптимизация +

+

+ Рекомендуется обновить 5 существующих правил для повышения + эффективности +

+
- - - {/* Recent Activity */} + + ) : ( + /* CTA Section для неавторизованных */
-
-

- Последние события -

- -
-
- {recentActivities.map((activity) => { - const severityStyles = getSeverityStyles(activity.severity); - const Icon = severityStyles.icon; - return ( -
-
-
- -
-
-

- {activity.message} -

-
- -

- {activity.time} -

-
-
-
-
- ); - })} -
- -
- - - {/* AI Insights */} -
-
-
-
- -
-
-

- AI Аналитика -

-

- Новые предложения на основе анализа логов -

-
-
+ Готовы начать? + +

+ Присоединяйтесь к IPS Manager и получите полный контроль над + безопасностью +

-
-
-

- 🔍 Обнаружена аномалия -

-

- Повышенная активность с IP-адресов из диапазона 185.xxx.xx.xx -

-
-
-

- 💡 Предложено правил -

-

- AI предлагает 3 новых правила для блокировки бот-сканеров -

-
-
-

- ⚡ Оптимизация -

-

- Рекомендуется обновить 5 существующих правил для повышения - эффективности -

-
-
-
+ )}
); -- 2.52.0 From c3c0e63fd5dc3d964e6405fb1619e7afdce007b1 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Sun, 14 Jun 2026 00:21:13 +0300 Subject: [PATCH 15/15] chore: update deploy --- .gitea/workflows/deploy.yml | 13 +++++----- infra/ansible/inventory/hosts.yml | 19 +++++++++----- infra/ansible/playbook.yml | 43 +++++++++++++++++++++---------- infra/ansible/requirements.yml | 6 +++-- 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index ea2cbd2..acc815f 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -2,7 +2,7 @@ name: Deploy on: push: - branches: [dev] + branches: [master] env: REGISTRY: gitea.d3m0k1d.ru @@ -16,11 +16,13 @@ jobs: - name: Setup SSH run: | mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_rsa - name: Install Ansible - run: apt update && apt install -y ansible + run: | + apt update && apt install -y ansible + ansible-galaxy install -r infra/ansible/requirements.yml - name: Login to registry run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login $REGISTRY -u "${{ secrets.REGISTRY_USER }}" --password-stdin @@ -35,8 +37,5 @@ jobs: - name: Deploy run: | echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > .vault_pass - ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbook.yml \ - --vault-password-file .vault_pass \ - -e registry=$REGISTRY \ - -e tag=latest + ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbook.yml --vault-password-file .vault_pass rm .vault_pass diff --git a/infra/ansible/inventory/hosts.yml b/infra/ansible/inventory/hosts.yml index acea417..c84a418 100644 --- a/infra/ansible/inventory/hosts.yml +++ b/infra/ansible/inventory/hosts.yml @@ -1,6 +1,13 @@ -all: - hosts: - prod: - ansible_host: - ansible_user: root - ansible_port: 22 +$ANSIBLE_VAULT;1.1;AES256 +63663666653739363337653532643363626133303030323462363762316364633838623636626636 +3163343137366530326139353638316466663037663935340a386362666236633237313939366639 +34626337346365663033386631366362366261366163646438646461376662666665363635396333 +3533626234383564390a663966376163366530643965306563363565326438313465383866343138 +66633432663430373339326365303033323133383365656231373736323234386435626431383639 +63396366333433343039343165633436633839666330646261633338666435353035656230313932 +33333630343535646338303539356532306632373433643536393537383463396330366634393962 +36356139616432336664613139623038373434643562353565353866303130323938383439396131 +30316139333733356462366464653964313264646632336566616536643438326433623363643465 +63343430373666356634323761363433666463366431343537613635363239636131643837353935 +64316633663334663536656137666330393034666661383165376365666633303764643439366461 +33386433643034643466 diff --git a/infra/ansible/playbook.yml b/infra/ansible/playbook.yml index 5468699..db63d6d 100644 --- a/infra/ansible/playbook.yml +++ b/infra/ansible/playbook.yml @@ -1,32 +1,47 @@ --- - name: Deploy Frontend hosts: prod - vars: - deploy_path: /opt/app + pre_tasks: + - name: Install docker + ansible.builtin.include_role: + name: geerlingguy.docker + + - name: Configure ufw + community.general.ufw: + rule: allow + port: "{{ item }}" + loop: + - "80" + - "443" + - "2222" + + - name: Enable ufw + community.general.ufw: + state: enabled tasks: - - name: Ensure deployment directory + - name: Ensure directory ansible.builtin.file: - path: "{{ deploy_path }}" + path: /opt/aegisfront state: directory - - name: Copy docker-compose + - name: Copy compose ansible.builtin.copy: src: "{{ playbook_dir }}/../docker-compose.yml" - dest: "{{ deploy_path }}/docker-compose.yml" + dest: /opt/aegisfront/docker-compose.yml - - name: Pull images + - name: Pull image ansible.builtin.shell: cmd: docker compose pull - chdir: "{{ deploy_path }}" + chdir: /opt/aegisfront environment: - REGISTRY: "{{ registry }}" - TAG: "{{ tag }}" + REGISTRY: gitea.d3m0k1d.ru + TAG: latest - - name: Start services + - name: Start ansible.builtin.shell: cmd: docker compose up -d --remove-orphans - chdir: "{{ deploy_path }}" + chdir: /opt/aegisfront environment: - REGISTRY: "{{ registry }}" - TAG: "{{ tag }}" + REGISTRY: gitea.d3m0k1d.ru + TAG: latest diff --git a/infra/ansible/requirements.yml b/infra/ansible/requirements.yml index 9bb092f..f643953 100644 --- a/infra/ansible/requirements.yml +++ b/infra/ansible/requirements.yml @@ -1,4 +1,6 @@ --- -roles: [] +roles: + - geerlingguy.docker -collections: [] +collections: + - community.general -- 2.52.0