` (hydration warning).
+- Login currently redirects to `http://localhost:8000/docs` after success
+ (placeholder — no post-login page yet); `ResetComponent` copies that
+ placeholder redirect too.
+
## Testing (`tests/unit`, `tests/integrated`, `tests/e2e`)
- **Recurring root cause of "different event loop" / `MissingGreenlet`-style
diff --git a/configs/.env.example b/configs/.env.example
index b4875b3..3e75494 100644
--- a/configs/.env.example
+++ b/configs/.env.example
@@ -25,5 +25,5 @@ SMTP_SERVER="change_me"
EMAIL_LOGIN="change_me"
EMAIL_PASSWORD="change_me"
-
+FRONT_URL="change_me"
PROD_MODE=bool
\ No newline at end of file
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/frontend/.yarnrc.yml b/frontend/.yarnrc.yml
new file mode 100644
index 0000000..063ed60
--- /dev/null
+++ b/frontend/.yarnrc.yml
@@ -0,0 +1,8 @@
+approvedGitRepositories:
+ - "**"
+
+enableScripts: true
+
+nodeLinker: node-modules
+
+npmMinimalAgeGate: 0
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/configs/.env.example b/frontend/configs/.env.example
new file mode 100644
index 0000000..e8d0ef0
--- /dev/null
+++ b/frontend/configs/.env.example
@@ -0,0 +1 @@
+VITE_BACKEND_URL="change_me"
\ No newline at end of file
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
new file mode 100644
index 0000000..ef614d2
--- /dev/null
+++ b/frontend/eslint.config.js
@@ -0,0 +1,22 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..0fca6f0
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
frontend
+
+
+
+
+
+
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..d8ea537
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "frontend",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "react-router-dom": "^7.18.4"
+ },
+ "devDependencies": {
+ "@eslint/js": "^10.0.1",
+ "@types/node": "^24.13.3",
+ "@types/react": "^19.2.18",
+ "@types/react-dom": "^19.2.7",
+ "@vitejs/plugin-react": "^6.1.1",
+ "eslint": "^10.10.0",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "eslint-plugin-react-refresh": "^0.5.6",
+ "globals": "^17.12.0",
+ "typescript": "~6.0.2",
+ "typescript-eslint": "^8.69.0",
+ "vite": "^8.3.0"
+ }
+}
diff --git a/frontend/src/App.css b/frontend/src/App.css
new file mode 100644
index 0000000..3fc8113
--- /dev/null
+++ b/frontend/src/App.css
@@ -0,0 +1,7 @@
+#root {
+ height: 100%;
+ box-shadow: 1px 1px 20px 1px rgba(68, 68, 68, 0.5);
+ border-radius: 40px;
+ border: 1px solid #444;
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 0000000..411f6b1
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,15 @@
+import './App.css'
+import { Route, Routes } from "react-router-dom";
+import { LoginPage } from "./pages/LoginPage";
+import { ForgotPasswordPage } from "./pages/ForgotPasswordPage";
+
+function App() {
+ return (
+
+ } />
+ } />
+
+ );
+}
+
+export default App
diff --git a/frontend/src/api/LoginAPI/Auth.ts b/frontend/src/api/LoginAPI/Auth.ts
new file mode 100644
index 0000000..97935c9
--- /dev/null
+++ b/frontend/src/api/LoginAPI/Auth.ts
@@ -0,0 +1,14 @@
+import { API_URL, setAccessToken } from "../token";
+
+export async function login(email: string, password: string) {
+ const body = new URLSearchParams({ username: email, password });
+ const res = await fetch(`${API_URL}/protected/token`, {
+ method: "POST",
+ body,
+ credentials: "include",
+ });
+ if (!res.ok) throw new Error("Неверный email или пароль");
+ const data = await res.json();
+ setAccessToken(data.access_token);
+ return data;
+}
diff --git a/frontend/src/api/ResetAPI/Reset.ts b/frontend/src/api/ResetAPI/Reset.ts
new file mode 100644
index 0000000..4912fae
--- /dev/null
+++ b/frontend/src/api/ResetAPI/Reset.ts
@@ -0,0 +1,11 @@
+
+export async function requestPasswordReset(email: string): Promise
{
+ const res = await fetch(`http://localhost:8000/protected/reset?${new URLSearchParams({ email })}`, {
+ method: "POST",
+ credentials: "include",
+ });
+ if (!res.ok) {
+ const body = await res.json().catch(() => null);
+ throw new Error(typeof body?.detail === "string" ? body.detail : "Не удалось отправить запрос");
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/api/token.ts b/frontend/src/api/token.ts
new file mode 100644
index 0000000..14bcfce
--- /dev/null
+++ b/frontend/src/api/token.ts
@@ -0,0 +1,17 @@
+export const API_URL = import.meta.env.VITE_BACKEND_URL;
+
+let accessToken: string | null = null;
+
+export function setAccessToken(token: string | null) {
+ accessToken = token;
+}
+
+export function getAccessToken() {
+ return accessToken;
+}
+
+export async function authFetch(path: string, init: RequestInit = {}) {
+ const headers = new Headers(init.headers);
+ if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
+ return fetch(`${API_URL}${path}`, { ...init, headers, credentials: "include" });
+}
diff --git a/frontend/src/assets/LOGO.png b/frontend/src/assets/LOGO.png
new file mode 100644
index 0000000..dd873a7
Binary files /dev/null and b/frontend/src/assets/LOGO.png differ
diff --git a/frontend/src/assets/PC.png b/frontend/src/assets/PC.png
new file mode 100644
index 0000000..7a2613d
Binary files /dev/null and b/frontend/src/assets/PC.png differ
diff --git a/frontend/src/assets/bg_login.png b/frontend/src/assets/bg_login.png
new file mode 100644
index 0000000..11695f1
Binary files /dev/null and b/frontend/src/assets/bg_login.png differ
diff --git a/frontend/src/assets/graphics.png b/frontend/src/assets/graphics.png
new file mode 100644
index 0000000..d7b9856
Binary files /dev/null and b/frontend/src/assets/graphics.png differ
diff --git a/frontend/src/assets/paper.png b/frontend/src/assets/paper.png
new file mode 100644
index 0000000..6ae78e9
Binary files /dev/null and b/frontend/src/assets/paper.png differ
diff --git a/frontend/src/features/auth/components/AuthComponent.module.css b/frontend/src/features/auth/components/AuthComponent.module.css
new file mode 100644
index 0000000..9724e21
--- /dev/null
+++ b/frontend/src/features/auth/components/AuthComponent.module.css
@@ -0,0 +1,157 @@
+.wrapper_1 form {
+ display: flex;
+ flex-direction: column;
+ background-color: whitesmoke;
+ width: 100%;
+ padding: 10%;
+ justify-content: space-between;
+ height: 100%;
+}
+.Title {
+ font-size: 32px;
+ font-weight: bold;
+ text-align: left;
+ line-height: 45px;
+ letter-spacing: 1.2px;
+
+}
+.Description{
+ font-size: medium;
+ font-size: 20px;
+ line-height: 30px;
+
+}
+.Fields {
+ line-height: 40px;
+ font-weight: bold;
+ font-size: 20px;
+
+}
+.wrapper_1 {
+ background-color: whitesmoke;
+ width: 40%;
+}
+.wrapper_1 input {
+ line-height: 40px;
+ border-radius: 16px;
+ border: 1px solid rgba(68, 68, 68, 0.3);
+ font-size: large;
+}
+
+.footer{
+ display: flex;
+ padding-top: 10%;
+ justify-content: space-between;
+}
+
+
+.submitbtn {
+ display: flex;
+ justify-content: center;
+ padding-top: 10%;
+ padding-bottom: 20px;
+}
+.submit {
+ background-color: #087A55;
+ height: 50px;
+ width: 100%;
+ color: white;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ font-size: larger;
+ font-weight: 500;
+}
+.submit-arrow {
+ width: 30px;
+ height: 30px;
+}
+.email-icon, .pass-icon {
+ height: 25px;
+ position: absolute;
+ left: 15px;
+ top: 50%;
+ transform: translateY(-50%);
+ pointer-events: none;
+ transition: opacity 0.2s ease;
+}
+.email_field, .pass_field {
+ position: relative;
+ width: 100%;
+}
+.email_field input, .pass_field input {
+ width: 100%;
+ padding: 12px 15px 12px 45px;
+ transition: padding-left 0.2s ease;
+}
+.pass_field input {
+ padding-right: 45px;
+}
+.toggle-password {
+ position: absolute;
+ right: 15px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+}
+.toggle-password svg {
+ width: 22px;
+ height: 22px;
+}
+.email_field input:focus,
+.email_field input:not(:placeholder-shown) {
+ padding-left: 15px;
+}
+.email_field input:focus + .email-icon,
+.email_field input:not(:placeholder-shown) + .email-icon {
+ opacity: 0;
+}
+
+.pass_field input:focus,
+.pass_field input:not(:placeholder-shown) {
+ padding-left: 15px;
+}
+.pass_field input:focus + .pass-icon,
+.pass_field input:not(:placeholder-shown) + .pass-icon {
+ opacity: 0;
+}
+.forgot_link {
+ color: #087A55;
+ font-weight: 600;
+
+}
+
+.remember-me label {
+
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 16px;
+ accent-color: #087A55;
+
+}
+.remember-me label input[type="checkbox"] {
+ width: 25px;
+ height: 25px;
+ border-radius: 10px;
+}
+
+.wrapper_1 .inputError {
+ border-color: #d93025;
+ background-color: #fdf2f1;
+}
+.errorText {
+ color: #d93025;
+ font-size: 14px;
+ padding-top: 6px;
+ position: absolute;
+ left: 4px;
+ top: 100%;
+}
diff --git a/frontend/src/features/auth/components/AuthComponent.tsx b/frontend/src/features/auth/components/AuthComponent.tsx
new file mode 100644
index 0000000..cfbd8f7
--- /dev/null
+++ b/frontend/src/features/auth/components/AuthComponent.tsx
@@ -0,0 +1,128 @@
+import { useState, type SubmitEvent, type ReactElement } from "react";
+import { Link } from "react-router-dom";
+import styles from "./AuthComponent.module.css";
+import { login } from "../../../api/LoginAPI/Auth";
+
+export function Auth():ReactElement {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [rememberMe, setRememberMe] = useState(false);
+ const [showPassword, setShowPassword] = useState(false);
+ const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
+
+ const validateEmail = (value: string) => {
+ if (!value) return "Введите email";
+ if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
+ return undefined;
+ };
+ const validatePassword = (value: string) =>
+ value ? undefined : "Введите пароль";
+
+ const handleSubmit = async (e: SubmitEvent) => {
+ e.preventDefault();
+ const next = { email: validateEmail(email), password: validatePassword(password) };
+ setErrors(next);
+ if (next.email || next.password) return;
+
+ try {
+ await login(email, password);
+ window.location.href = "http://localhost:8000/docs";
+ } catch (err) {
+ console.error(err);
+ setErrors({ password: "Неверный email или пароль" });
+ }
+ };
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/frontend/src/features/auth/components/ResetComponent.module.css b/frontend/src/features/auth/components/ResetComponent.module.css
new file mode 100644
index 0000000..dc3e6d3
--- /dev/null
+++ b/frontend/src/features/auth/components/ResetComponent.module.css
@@ -0,0 +1,105 @@
+.wrapper_1 form {
+ display: flex;
+ flex-direction: column;
+ background-color: whitesmoke;
+ width: 100%;
+ padding: 10%;
+ justify-content: flex-start;
+ gap: 24px;
+ height: 100%;
+}
+.wrapper_1 input {
+ line-height: 40px;
+ border-radius: 16px;
+ border: 1px solid rgba(68, 68, 68, 0.3);
+ font-size: large;
+}
+.Title {
+ font-size: 32px;
+ font-weight: bold;
+ text-align: left;
+ line-height: 45px;
+ letter-spacing: 1.2px;
+}
+.Description {
+ font-size: 20px;
+ line-height: 30px;
+}
+.Fields {
+ line-height: 40px;
+ font-weight: bold;
+ font-size: 20px;
+}
+.wrapper_1 {
+ background-color: whitesmoke;
+ width: 40%;
+}
+.email_field {
+ position: relative;
+ width: 100%;
+}
+.email_field input {
+ width: 100%;
+ padding: 12px 15px 12px 45px;
+ transition: padding-left 0.2s ease;
+}
+.email_field input:focus,
+.email_field input:not(:placeholder-shown) {
+ padding-left: 15px;
+}
+.email_field input:focus + .email-icon,
+.email_field input:not(:placeholder-shown) + .email-icon {
+ opacity: 0;
+}
+.email-icon {
+ height: 25px;
+ position: absolute;
+ left: 15px;
+ top: 50%;
+ transform: translateY(-50%);
+ pointer-events: none;
+ transition: opacity 0.2s ease;
+}
+.submitbtn {
+ display: flex;
+ justify-content: center;
+ padding-top: 10%;
+ padding-bottom: 20px;
+}
+.submit {
+ background-color: #087A55;
+ height: 50px;
+ width: 100%;
+ color: white;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ font-size: larger;
+ font-weight: 500;
+}
+.submit-arrow {
+ width: 30px;
+ height: 30px;
+}
+.footer {
+ display: flex;
+ justify-content: center;
+}
+.login_link {
+ color: #087A55;
+ font-weight: 600;
+}
+
+.wrapper_1 .inputError {
+ border-color: #d93025;
+ background-color: #fdf2f1;
+}
+.errorText {
+ color: #d93025;
+ font-size: 14px;
+ position: absolute;
+ top: 100%;
+ left: 4px;
+}
\ No newline at end of file
diff --git a/frontend/src/features/auth/components/ResetComponent.tsx b/frontend/src/features/auth/components/ResetComponent.tsx
new file mode 100644
index 0000000..abf25cd
--- /dev/null
+++ b/frontend/src/features/auth/components/ResetComponent.tsx
@@ -0,0 +1,74 @@
+import styles from "./ResetComponent.module.css";
+import { useState, type ReactElement, type SubmitEvent } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import { requestPasswordReset } from "../../../api/ResetAPI/Reset";
+
+export function Reset():ReactElement {
+ const navigate = useNavigate();
+ const [email, setEmail] = useState("");
+ const [errors, setErrors] = useState<{ email?: string;}>({});
+
+ const validateEmail = (value: string) => {
+ if (!value) return "Введите email";
+ if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
+ return undefined;
+ };
+ const handleSubmit = async (e: SubmitEvent) => {
+ e.preventDefault();
+ const next = {
+ email: validateEmail(email),
+ };
+ setErrors(next);
+ if (next.email) return;
+ try {
+ await requestPasswordReset(email);
+ navigate("/");
+ } catch (err) {
+ console.error(err);
+ setErrors({ email: "Ошибка, попробуйте позже" });
+ }
+ };
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000..6254f3c
--- /dev/null
+++ b/frontend/src/index.css
@@ -0,0 +1,24 @@
+@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap');
+
+*{padding: 0;margin: 0;border: 0;}
+*,*:before,*:after{-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;}
+:focus,:active{outline: none;}
+a:focus,a:active{outline: none;}
+nav,footer,header,aside{display: block;}
+html,body{
+ height:100%;width:100%;
+ padding: 30px;
+ font-family: 'Manrope', sans-serif;
+ user-select: none;
+ font-size:100%;line-height:1;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;-webkit-text-size-adjust:100%;}
+input,button,textarea{font-family:inherit;}
+input::-ms-clear{display: none;}
+button{cursor: pointer;}
+button::-moz-focus-inner{padding:0;border:0;}
+a,a:visited{text-decoration: none;}
+a:hover{text-decoration: none;}
+ul li{list-style: none;}
+img{vertical-align: top;}
+h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight: inherit;}
+a{color:inherit;}
+
diff --git a/frontend/src/layouts/AuthLayout.css b/frontend/src/layouts/AuthLayout.css
new file mode 100644
index 0000000..1cfbfec
--- /dev/null
+++ b/frontend/src/layouts/AuthLayout.css
@@ -0,0 +1,96 @@
+.wrapper {
+ display: flex;
+ flex-direction: row;
+ min-height: auto;
+ width: 100%;
+ overflow: hidden;
+ height: 100%;
+}
+.left_side {
+ width: 60%;
+ background-image: url("../assets/bg_login.png");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ border-right: 1px solid rgba(68, 68, 68, 0.3);
+}
+
+.container {
+ padding: 40px 20px;
+ padding-bottom: 20px;
+}
+.img_logo {
+ width: 133px;
+ height: 82px;
+}
+.header_top {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+.header_title {
+ font-size: 36px;
+ font-weight: 900;
+}
+.header_heading {
+ font-size: 28px;
+ color: rgba(0, 0, 0, 0.5);
+}
+.header_bottom {
+ font-size: 20px;
+ color: rgba(0, 0, 0, 0.8);
+ line-height: 30px;
+ padding-top: 20px;
+ padding-left: 30px;
+}
+.main_item_img {
+ width: 75%;
+ height: 70%;
+}
+.main_items {
+ display: flex;
+ justify-content: space-between;
+}
+.main_item {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+.main_item_title {
+ font-size: 20px;
+ font-weight: 600;
+}
+.main_item_p {
+ padding-top: 10px;
+ font-size: 16px;
+ width: 50%;
+ text-align: center;
+ line-height: 25px;
+}
+.section_item > svg {
+ width: 40px;
+ height: 40px;
+ margin-bottom: 15px;
+}
+.section_items {
+ display: flex;
+ justify-content: space-between;
+}
+.section {
+ padding: 7% 40px 0px 0px;
+}
+.section_item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ width: 25%;
+ line-height: 25px;
+ font-weight: 900;
+ font-size: 16px;
+}
+.section_line {
+ border: 1px solid rgba(0, 0, 0, 0.1);
+}
diff --git a/frontend/src/layouts/AuthLayout.tsx b/frontend/src/layouts/AuthLayout.tsx
new file mode 100644
index 0000000..55edc2a
--- /dev/null
+++ b/frontend/src/layouts/AuthLayout.tsx
@@ -0,0 +1,109 @@
+import type { ReactNode } from "react";
+import "./AuthLayout.css";
+import logo from "../assets/LOGO.png";
+import img_1 from "../assets/paper.png";
+import img_2 from "../assets/PC.png";
+import img_3 from "../assets/graphics.png";
+
+export function AuthLayout({ children }: { children: ReactNode }) {
+ return (
+
+
+
+
+
+
+
+
+ The DisExcel
+
+
+ Project
+
+
+
+
+
+ Избавляем от ручной работы.
+ Переводим процессы из бумаги в Excel,
+ а из Excel - в 1С и отчеты.
+
+
+
+
+
+
+
+ {/* 1 */}
+
+
+
Бумага
+
+ Ручная работа, ошибки, потери
+
+
+
+ {/* 2 */}
+
+
+
Excel
+
+ Структура, формулы, автоматизация
+
+
+
+ {/* 3 */}
+
+
+
1С / Отчеты
+
+ Готовые процессы, аналитика, контроль
+
+
+
+
+
+
+
+ {/* 1 */}
+
+
+
+
+
+ Экономим
время
+
+
+ {/* 2 */}
+
+
+
+
+
+ Снижаем
ошибки
+
+
+ {/* 3 */}
+
+
+
+
+
+ Автоматизируем
процессы
+
+
+ {/* 4 */}
+
+
+
+
+
+ Делаем
бизнес эффективнее
+
+
+
+
+ {children}
+
+ );
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 0000000..ade9d64
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,13 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import { BrowserRouter } from 'react-router-dom'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+
+ ,
+)
diff --git a/frontend/src/pages/ForgotPasswordPage.tsx b/frontend/src/pages/ForgotPasswordPage.tsx
new file mode 100644
index 0000000..4a657d1
--- /dev/null
+++ b/frontend/src/pages/ForgotPasswordPage.tsx
@@ -0,0 +1,10 @@
+import { Reset } from "../features/auth/components/ResetComponent";
+import { AuthLayout } from "../layouts/AuthLayout";
+
+export function ForgotPasswordPage() {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx
new file mode 100644
index 0000000..1171ed6
--- /dev/null
+++ b/frontend/src/pages/LoginPage.tsx
@@ -0,0 +1,10 @@
+import { Auth } from "../features/auth/components/AuthComponent";
+import { AuthLayout } from "../layouts/AuthLayout";
+
+export function LoginPage(){
+ return(
+
+
+
+ );
+}
diff --git a/frontend/src/types/UserTypes.ts b/frontend/src/types/UserTypes.ts
new file mode 100644
index 0000000..29e13d1
--- /dev/null
+++ b/frontend/src/types/UserTypes.ts
@@ -0,0 +1,25 @@
+interface PermissionsOut {
+ id: number
+ permission: string
+}
+
+interface PermissionsGroupsOut {
+ id: number
+ group: string
+ permissions: PermissionsOut[]
+}
+
+export interface UserOut {
+ first_name: string
+ last_name: string
+ middle_name: string
+ email: string
+
+ direct_permissions: PermissionsOut[]
+ group: PermissionsGroupsOut[]
+}
+
+export interface UserIn {
+ email: string
+ plain_password:string
+}
\ No newline at end of file
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
new file mode 100644
index 0000000..6830b6f
--- /dev/null
+++ b/frontend/tsconfig.app.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "es2023",
+ "lib": ["ES2023", "DOM"],
+ "module": "esnext",
+ "types": ["vite/client"],
+ "allowArbitraryExtensions": true,
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json
new file mode 100644
index 0000000..8455dcb
--- /dev/null
+++ b/frontend/tsconfig.node.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "es2023",
+ "lib": ["ES2023"],
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "module": "nodenext",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 0000000..20b9100
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,8 @@
+import react from '@vitejs/plugin-react'
+import { defineConfig } from 'vite'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ envDir:"./configs",
+})
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
new file mode 100644
index 0000000..1d52115
--- /dev/null
+++ b/frontend/yarn.lock
@@ -0,0 +1,1285 @@
+# 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", "@babel/generator@^7.29.8":
+ version "7.29.8"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.8.tgz#4b0b887885422643339e09022148a4c4ebaa4979"
+ integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==
+ dependencies:
+ "@babel/parser" "^7.29.8"
+ "@babel/types" "^7.29.8"
+ "@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", "@babel/parser@^7.29.8":
+ version "7.29.9"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.9.tgz#e7ee0a24f752c5296c6455855ccb72662e67ea96"
+ integrity sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==
+ dependencies:
+ "@babel/types" "^7.29.8"
+
+"@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.8"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.8.tgz#4111014cdc71a0f95d9471907590baa0b8a6b28a"
+ integrity sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==
+ dependencies:
+ "@babel/code-frame" "^7.29.7"
+ "@babel/generator" "^7.29.8"
+ "@babel/helper-globals" "^7.29.7"
+ "@babel/parser" "^7.29.8"
+ "@babel/template" "^7.29.7"
+ "@babel/types" "^7.29.8"
+ debug "^4.3.1"
+
+"@babel/types@^7.29.7", "@babel/types@^7.29.8":
+ version "7.29.8"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863"
+ integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.29.7"
+ "@babel/helper-validator-identifier" "^7.29.7"
+
+"@cacheable/memory@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@cacheable/memory/-/memory-2.2.0.tgz#72aeb8b051f6d597d10ac8595b94ad8302bd2239"
+ integrity sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==
+ dependencies:
+ "@cacheable/utils" "^2.5.0"
+ "@keyv/bigmap" "^1.3.1"
+ hookified "^1.15.1"
+ keyv "^5.6.0"
+
+"@cacheable/utils@^2.5.0":
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/@cacheable/utils/-/utils-2.5.0.tgz#534c91113aa48fe43baedb169550b6ee070ef303"
+ integrity sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==
+ dependencies:
+ hashery "^1.5.1"
+ keyv "^5.6.0"
+
+"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1":
+ version "4.10.1"
+ resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6"
+ integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==
+ 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.7.0":
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.7.0.tgz#09ee4aa07b73f059ec2d4c74bf4b2ff02b322377"
+ integrity sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==
+ 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.3":
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz#cc7268cc36405b331ef92db1bc37971f21d66fe1"
+ integrity sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==
+ 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":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz#f4c663e862f06dc98ca4d453862c46902789a18d"
+ integrity sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==
+
+"@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"
+
+"@keyv/bigmap@^1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@keyv/bigmap/-/bigmap-1.3.1.tgz#fc82fa83947e7ff68c6798d08907db842771ef2c"
+ integrity sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==
+ dependencies:
+ hashery "^1.4.0"
+ hookified "^1.15.0"
+
+"@keyv/serialize@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.1.1.tgz#0c01dd3a3483882af7cf3878d4e71d505c81fc4a"
+ integrity sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==
+
+"@oxc-project/types@=0.150.0":
+ version "0.150.0"
+ resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.150.0.tgz#e9be0840114655016b16f1c05ddaaa01df5b8853"
+ integrity sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==
+
+"@rolldown/binding-android-arm-eabi@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz#b38df5a6997454696207ac16b322a3ac1ff79e16"
+ integrity sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==
+
+"@rolldown/binding-android-arm64@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz#bc2bf135c1e787f94b29bc321c1db0bd2884b5a7"
+ integrity sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==
+
+"@rolldown/binding-darwin-arm64@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz#052049707ccc4cfe0b035aa8c37c4e55523eecbb"
+ integrity sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==
+
+"@rolldown/binding-darwin-x64@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz#ea67496811e4e1c2f76d5647f248073e204f5462"
+ integrity sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==
+
+"@rolldown/binding-freebsd-x64@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz#4b4a605f8c24a7235afb5da9b4b8bc8974446bb5"
+ integrity sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==
+
+"@rolldown/binding-linux-arm-gnueabihf@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz#a36c6961fb24b86176463559dcd0edbe48ea74f1"
+ integrity sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==
+
+"@rolldown/binding-linux-arm64-gnu@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz#7b43488618dd6f9d392e9adaaffd4d7f4bb785ce"
+ integrity sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==
+
+"@rolldown/binding-linux-arm64-musl@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz#8168a2e2ce7d9e28847f3019c98abf76c0ec5fe2"
+ integrity sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==
+
+"@rolldown/binding-linux-ppc64-gnu@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz#8f52aac40788feac33056f111176a7bf8fbc297a"
+ integrity sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==
+
+"@rolldown/binding-linux-s390x-gnu@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz#80cdfa507fd87bc37c47c5a16ca578f619ee3a69"
+ integrity sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==
+
+"@rolldown/binding-linux-x64-gnu@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz#c2f89cdf3b223b2ba3cd4c0fdc760b360c02edf5"
+ integrity sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==
+
+"@rolldown/binding-linux-x64-musl@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz#3fbb6bce4c8a8260cb63fc2dba182c8649930c6c"
+ integrity sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==
+
+"@rolldown/binding-openharmony-arm64@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz#c8cd18d7fd14222e2068cd0640bba88d2cd710f8"
+ integrity sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==
+
+"@rolldown/binding-win32-arm64-msvc@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz#cff9ed3be36979d7d77306bf9dca1a27ed17c1f0"
+ integrity sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==
+
+"@rolldown/binding-win32-x64-msvc@1.2.9":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz#6e6a3a7ac86eb46bed61e685a9e7cccfacc21b7a"
+ integrity sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==
+
+"@rolldown/pluginutils@^1.0.0", "@rolldown/pluginutils@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz#e3fcee093fbb5ce765e1ad088ff4de2889f6f9be"
+ integrity sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==
+
+"@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.13.3":
+ version "24.13.6"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-24.13.6.tgz#30ba2d8aae3c321fd170fe52a915231a67a4875c"
+ integrity sha512-SGrw/h3KPFshy3OE6ZL53LMBG5vGQQ8/gIpiqz/kRZhPJ7HgwCEs8LBuNtWLa8dvGZVpSF7+Bf+c11HUrCb/yg==
+ dependencies:
+ undici-types "~7.18.0"
+
+"@types/react-dom@^19.2.7":
+ version "19.3.0"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.3.0.tgz#90294a9266efd928a5266f2110d60d5dba5015d2"
+ integrity sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==
+
+"@types/react@^19.2.18":
+ version "19.3.0"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-19.3.0.tgz#3b3ce8d8c1627fb5ccd4dadd57ef445c5a153b77"
+ integrity sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==
+ dependencies:
+ csstype "^3.2.2"
+
+"@typescript-eslint/eslint-plugin@8.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.0.tgz#59f635c74dd1e2bffb6aecbda557b24dadffd3dc"
+ integrity sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==
+ dependencies:
+ "@eslint-community/regexpp" "^4.12.2"
+ "@typescript-eslint/scope-manager" "8.70.0"
+ "@typescript-eslint/type-utils" "8.70.0"
+ "@typescript-eslint/utils" "8.70.0"
+ "@typescript-eslint/visitor-keys" "8.70.0"
+ ignore "^7.0.5"
+ natural-compare "^1.4.0"
+ ts-api-utils "^2.5.0"
+
+"@typescript-eslint/parser@8.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.70.0.tgz#96ce2de96c06c8442fea1a8f7b07855a56b3c5e3"
+ integrity sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==
+ dependencies:
+ "@typescript-eslint/scope-manager" "8.70.0"
+ "@typescript-eslint/types" "8.70.0"
+ "@typescript-eslint/typescript-estree" "8.70.0"
+ "@typescript-eslint/visitor-keys" "8.70.0"
+ debug "^4.4.3"
+
+"@typescript-eslint/project-service@8.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.70.0.tgz#a62e837362f26c604ad15b20bacce1c3f4d6b552"
+ integrity sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==
+ dependencies:
+ "@typescript-eslint/tsconfig-utils" "^8.70.0"
+ "@typescript-eslint/types" "^8.70.0"
+ debug "^4.4.3"
+
+"@typescript-eslint/scope-manager@8.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.70.0.tgz#b77628b03c9c56ef21fb5a6bc2f4a4335163b999"
+ integrity sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==
+ dependencies:
+ "@typescript-eslint/types" "8.70.0"
+ "@typescript-eslint/visitor-keys" "8.70.0"
+
+"@typescript-eslint/tsconfig-utils@8.70.0", "@typescript-eslint/tsconfig-utils@^8.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.0.tgz#f583ca72159c4fd8e775c153da3241de6b77974f"
+ integrity sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==
+
+"@typescript-eslint/type-utils@8.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.70.0.tgz#7f9c01c24e56bfad2a089167926c7cdded9de5f6"
+ integrity sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==
+ dependencies:
+ "@typescript-eslint/types" "8.70.0"
+ "@typescript-eslint/typescript-estree" "8.70.0"
+ "@typescript-eslint/utils" "8.70.0"
+ debug "^4.4.3"
+ ts-api-utils "^2.5.0"
+
+"@typescript-eslint/types@8.70.0", "@typescript-eslint/types@^8.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.70.0.tgz#9ee52888cdeca604fe9436935219b967fa7f6053"
+ integrity sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==
+
+"@typescript-eslint/typescript-estree@8.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.0.tgz#79cfcd9678ee28ea69cc13032c0f89bb1d298917"
+ integrity sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==
+ dependencies:
+ "@typescript-eslint/project-service" "8.70.0"
+ "@typescript-eslint/tsconfig-utils" "8.70.0"
+ "@typescript-eslint/types" "8.70.0"
+ "@typescript-eslint/visitor-keys" "8.70.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.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.70.0.tgz#78a78b4c52dd8523e5321993cb46ebe6d7934510"
+ integrity sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.9.1"
+ "@typescript-eslint/scope-manager" "8.70.0"
+ "@typescript-eslint/types" "8.70.0"
+ "@typescript-eslint/typescript-estree" "8.70.0"
+
+"@typescript-eslint/visitor-keys@8.70.0":
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.0.tgz#b451c8aea76dc97fc768b8d9d7f61019bf789628"
+ integrity sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==
+ dependencies:
+ "@typescript-eslint/types" "8.70.0"
+ eslint-visitor-keys "^5.0.0"
+
+"@vitejs/plugin-react@^6.1.1":
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz#47fa1a2f956e88eaf778ebf65ddce8e2b36597e7"
+ integrity sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==
+ dependencies:
+ "@rolldown/pluginutils" "^1.0.1"
+
+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.18.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.18.0.tgz#4faf01b2d6d326bfeed97aea1f52220b5f4c1940"
+ integrity sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==
+
+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"
+
+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.11.23:
+ version "2.11.25"
+ resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz#88b942ce372bdf2d6a2652136bbf7826c65c77be"
+ integrity sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==
+
+brace-expansion@^5.0.8:
+ version "5.0.12"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.12.tgz#995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec"
+ integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==
+ dependencies:
+ balanced-match "^4.0.2"
+
+browserslist@^4.24.0:
+ version "4.29.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.29.0.tgz#e50e72c30c4b5f7ec894c09feb6ed525167f5344"
+ integrity sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==
+ dependencies:
+ baseline-browser-mapping "^2.11.23"
+ caniuse-lite "^1.0.30001810"
+ electron-to-chromium "^1.5.427"
+ node-releases "^2.0.55"
+ update-browserslist-db "^1.3.3"
+
+cacheable@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/cacheable/-/cacheable-2.5.0.tgz#d142d41043e5a865f6053cc70ef4e3ad068bd5ce"
+ integrity sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==
+ dependencies:
+ "@cacheable/memory" "^2.2.0"
+ "@cacheable/utils" "^2.5.0"
+ hookified "^1.15.0"
+ keyv "^5.6.0"
+ qified "^0.10.1"
+
+caniuse-lite@^1.0.30001810:
+ version "1.0.30001810"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz#4970b477dea3278374de9bc43aa8f5d39fc3cda2"
+ integrity sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==
+
+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.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==
+
+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==
+
+electron-to-chromium@^1.5.427:
+ version "1.5.433"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.433.tgz#5c26b05c48030ff4a5566fcaa10dc452a1d1de3e"
+ integrity sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==
+
+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.6:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.7.tgz#acff4138192efd299114c8f101de1088416054c2"
+ integrity sha512-XhJSzLljuYD4UjNuFGJu2v7aD3sqNVK12w5/DCUfrNfHWegZo2+TsavNx8MuxMwEquuFAzNbNfXKU0WoZ+YUIg==
+
+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.10.0:
+ version "10.11.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.11.0.tgz#304d1591b7c6a327e3f64b4f550f99fda160ae20"
+ integrity sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.8.0"
+ "@eslint-community/regexpp" "^4.12.2"
+ "@eslint/config-array" "^0.23.5"
+ "@eslint/config-helpers" "^0.7.0"
+ "@eslint/core" "^1.2.1"
+ "@eslint/plugin-kit" "^0.7.3"
+ "@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 "11.1.5 || >11.1.6 <12"
+ 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.5"
+ 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@11.1.5 || >11.1.6 <12":
+ version "11.1.5"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-11.1.5.tgz#c8210eb055de63e68685ccfb6a017e386d4577d0"
+ integrity sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==
+ dependencies:
+ flat-cache "^6.1.23"
+
+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@^6.1.23:
+ version "6.1.23"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-6.1.23.tgz#735dc888c271868d7301b3bbb8edba5028afb61d"
+ integrity sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==
+ dependencies:
+ cacheable "^2.5.0"
+ flatted "^3.4.2"
+ hookified "^1.15.0"
+
+flatted@^3.4.2:
+ version "3.4.4"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.4.tgz#aeeca2a506303f0cee61c59e6c9f2a88d2f29fc6"
+ integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==
+
+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==
+
+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==
+
+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.12.0:
+ version "17.12.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-17.12.0.tgz#910a368b8f093d3feb3f77a56dcefa05647d698e"
+ integrity sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==
+
+hashery@^1.4.0, hashery@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/hashery/-/hashery-1.5.1.tgz#4ba82ad54911ac617467870845d57a9fe508a400"
+ integrity sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==
+ dependencies:
+ hookified "^1.15.0"
+
+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"
+
+hookified@^1.15.0, hookified@^1.15.1:
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/hookified/-/hookified-1.15.1.tgz#b1fafeaa5489cdc29cb85546a8f837ed4ffbbcb6"
+ integrity sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==
+
+hookified@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/hookified/-/hookified-2.2.0.tgz#1d024ac1668973dd5bcc4a96ab9ccdb7639ef8d4"
+ integrity sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==
+
+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.9"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.9.tgz#475b2197ade916edab05ade35c691518e8543382"
+ integrity sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==
+
+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==
+
+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-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@^5.6.0:
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-5.6.0.tgz#03044074c6b4d072d0a62c7b9fa649537baf0105"
+ integrity sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==
+ dependencies:
+ "@keyv/serialize" "^1.1.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.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz#9a6841f88ae50fc83502903892b41af41bc2b907"
+ integrity sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==
+
+lightningcss-darwin-arm64@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz#c0f2c31c0bfd19fa4dd3f18e957a1f1a152097d6"
+ integrity sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==
+
+lightningcss-darwin-x64@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz#cb0705965acb538c6683949ce6925fb3cdf7c361"
+ integrity sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==
+
+lightningcss-freebsd-x64@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz#763538828b26bab2680dadafcc84ee78b0eb502b"
+ integrity sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==
+
+lightningcss-linux-arm-gnueabihf@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz#6862e3176a331aedbdec1ed352b4d7d0dd0784de"
+ integrity sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==
+
+lightningcss-linux-arm64-gnu@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz#c6a3a2ed15141daf6bdc2628930f8e39bdf473aa"
+ integrity sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==
+
+lightningcss-linux-arm64-musl@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz#7fa1334971fc82845f9827df6ef8a0b20914bac6"
+ integrity sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==
+
+lightningcss-linux-x64-gnu@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz#8b927862ea8c2bbc6831a46509244b50d9936e55"
+ integrity sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==
+
+lightningcss-linux-x64-musl@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz#0c525bb077dfd94404c059cfe42dad797e96aeaf"
+ integrity sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==
+
+lightningcss-win32-arm64-msvc@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz#850ee1103dac989cfab50e3ac22d1a69e394e63d"
+ integrity sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==
+
+lightningcss-win32-x64-msvc@1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz#e343ae152eed3609dc6e11949d1a3bf39a1c946f"
+ integrity sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==
+
+lightningcss@^1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.33.0.tgz#c08867d71a79385c6e190214fd72fef3e5f95f0b"
+ integrity sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==
+ dependencies:
+ detect-libc "^2.0.3"
+ optionalDependencies:
+ lightningcss-android-arm64 "1.33.0"
+ lightningcss-darwin-arm64 "1.33.0"
+ lightningcss-darwin-x64 "1.33.0"
+ lightningcss-freebsd-x64 "1.33.0"
+ lightningcss-linux-arm-gnueabihf "1.33.0"
+ lightningcss-linux-arm64-gnu "1.33.0"
+ lightningcss-linux-arm64-musl "1.33.0"
+ lightningcss-linux-x64-gnu "1.33.0"
+ lightningcss-linux-x64-musl "1.33.0"
+ lightningcss-win32-arm64-msvc "1.33.0"
+ lightningcss-win32-x64-msvc "1.33.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"
+
+minimatch@^10.2.2, minimatch@^10.2.4, minimatch@^10.2.5:
+ version "10.2.6"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef"
+ integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==
+ dependencies:
+ brace-expansion "^5.0.8"
+
+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.18:
+ version "3.3.19"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.19.tgz#336d4aa4bcd4fb24d2cddede7ffeae40bec03f0a"
+ integrity sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==
+
+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.55:
+ version "2.0.56"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.56.tgz#fff6a4093fe05d994a2a5e415eee5fcef874e6dc"
+ integrity sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==
+
+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, picomatch@^4.0.7:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f"
+ integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==
+
+postcss@^8.5.28:
+ version "8.5.28"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.28.tgz#da4563a99a06e62d6c1cd1acae363224bcaed6e9"
+ integrity sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==
+ dependencies:
+ nanoid "^3.3.18"
+ 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==
+
+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==
+
+qified@^0.10.1:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/qified/-/qified-0.10.1.tgz#0640bf21bbe6ca540db290ad8f5fde4d870b8bda"
+ integrity sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==
+ dependencies:
+ hookified "^2.1.1"
+
+react-dom@^19.2.8:
+ version "19.3.0"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.3.0.tgz#a0ff19161d7f4e9be5469a07278b0c06e00f9001"
+ integrity sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==
+ dependencies:
+ scheduler "^0.28.0"
+
+react-router-dom@^7.18.4:
+ version "7.18.4"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.18.4.tgz#07b72b8aab2d80af847425107f966f1688c703d0"
+ integrity sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==
+ dependencies:
+ react-router "7.18.4"
+
+react-router@7.18.4:
+ version "7.18.4"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.18.4.tgz#dc30cbb27e936f06d9e1d75ab648b317deb698a4"
+ integrity sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==
+ dependencies:
+ cookie "^1.0.1"
+ set-cookie-parser "^2.6.0"
+
+react@^19.2.8:
+ version "19.3.0"
+ resolved "https://registry.yarnpkg.com/react/-/react-19.3.0.tgz#e20aa3a14687075ce863351b5878b0608c3fbfb2"
+ integrity sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==
+
+rolldown@~1.2.6:
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.9.tgz#b449d73b7aeaf0097adfc25967a76fa1bf304c56"
+ integrity sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==
+ dependencies:
+ "@oxc-project/types" "=0.150.0"
+ "@rolldown/pluginutils" "^1.0.0"
+ optionalDependencies:
+ "@rolldown/binding-android-arm-eabi" "1.2.9"
+ "@rolldown/binding-android-arm64" "1.2.9"
+ "@rolldown/binding-darwin-arm64" "1.2.9"
+ "@rolldown/binding-darwin-x64" "1.2.9"
+ "@rolldown/binding-freebsd-x64" "1.2.9"
+ "@rolldown/binding-linux-arm-gnueabihf" "1.2.9"
+ "@rolldown/binding-linux-arm64-gnu" "1.2.9"
+ "@rolldown/binding-linux-arm64-musl" "1.2.9"
+ "@rolldown/binding-linux-ppc64-gnu" "1.2.9"
+ "@rolldown/binding-linux-s390x-gnu" "1.2.9"
+ "@rolldown/binding-linux-x64-gnu" "1.2.9"
+ "@rolldown/binding-linux-x64-musl" "1.2.9"
+ "@rolldown/binding-openharmony-arm64" "1.2.9"
+ "@rolldown/binding-win32-arm64-msvc" "1.2.9"
+ "@rolldown/binding-win32-x64-msvc" "1.2.9"
+
+scheduler@^0.28.0:
+ version "0.28.0"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.28.0.tgz#03d3ca348aeaf8b7502f64468f217b27523f9695"
+ integrity sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==
+
+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.5"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69"
+ integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
+
+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==
+
+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==
+
+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.69.0:
+ version "8.70.0"
+ resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.70.0.tgz#6c90c532079132ba7d4a386bf0882c56d82fe5af"
+ integrity sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==
+ dependencies:
+ "@typescript-eslint/eslint-plugin" "8.70.0"
+ "@typescript-eslint/parser" "8.70.0"
+ "@typescript-eslint/typescript-estree" "8.70.0"
+ "@typescript-eslint/utils" "8.70.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.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz#197e21fb2561fa89f8b94fad605a2b296a18993a"
+ integrity sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==
+ 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.3.0:
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-8.3.0.tgz#f9565cfd4879d58d28fa64d3c7aff3b372d2ccc8"
+ integrity sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==
+ dependencies:
+ lightningcss "^1.33.0"
+ picomatch "^4.0.7"
+ postcss "^8.5.28"
+ rolldown "~1.2.6"
+ 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.6.5"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-4.6.5.tgz#e3af3edbdc260239c52f73caad05dbbffe6e82cd"
+ integrity sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==
diff --git a/main.py b/main.py
index 450a22b..c55bb37 100644
--- a/main.py
+++ b/main.py
@@ -3,6 +3,7 @@ from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
from src.cache.redis_client import redis_client
from src.database.users.crud import Seed
@@ -10,6 +11,7 @@ from src.logging.http_logger import LoggingMiddleware, ProcessingTimeMiddleware
from src.logging.logger import LogWriter
from src.messaging.rabbitmq_client import rabbitmq_client
from src.messaging.topology_setup import apply_topology
+from src.models.configs_read.env import env_settings
from src.models.rabbitmq_models.email import email_topology
from src.web.protected_routes.auth_routes import router as protected_router
from src.web.protected_routes.protected_user_action_routes import (
@@ -35,6 +37,13 @@ app.add_middleware(LoggingMiddleware)
app.add_middleware(ProcessingTimeMiddleware)
app.include_router(router=protected_router)
app.include_router(router=protected_user_action_routes)
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=[env_settings.FRONT_URL],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
@app.get("")
diff --git a/readme.md b/readme.md
index 92d0427..86cb091 100644
--- a/readme.md
+++ b/readme.md
@@ -13,6 +13,7 @@ A FastAPI project combining Excel and digital data ("The Great Excel project tha
- Redis — caching, rate limiting, token revocation
- RabbitMQ (aio-pika) — background email workers
- Jinja2 — HTML email templates
+- Frontend: React 19, TypeScript, Vite, react-router-dom, CSS Modules (yarn)
- Docker, Ansible — deployment
## Architecture
@@ -30,6 +31,8 @@ src/
├── reports/ # reports
├── service/ # business logic (auth, users_crud, email sending)
└── web/ # routes (protected_routes)
+
+frontend/ # React app (login / password-reset pages), see "Frontend"
```
Layers are connected top to bottom: `web → service → database → models`.
@@ -60,6 +63,31 @@ python daemon_run.py --all # all daemons enabled in configs/daemons.js
- Refresh token is stored in the DB as a SHA256 hash, with rotation and revocation support
- Passwords are hashed with bcrypt
- RBAC: direct user permissions + permissions via groups, checked through `require_permissions()`
+- Login: `POST /protected/token` (OAuth2 form-data: `username` = email, `password`); returns the access token and sets the refresh token as an httpOnly cookie. Failed logins are rate-limited (5 per minute per IP)
+- Password reset: `POST /protected/reset?email=...` generates a temporary password, saves it and queues an email with it (3 requests per 10 minutes per IP). The response is the same whether or not the email exists
+- CORS: the allowed frontend origin is taken from the `FRONT_URL` variable in `configs/.env` (required, e.g. `http://localhost:5173`)
+
+## Frontend
+
+React + Vite app in `frontend/` (login and password-reset pages).
+
+```bash
+cd frontend
+yarn install
+yarn dev # http://localhost:5173
+```
+
+Environment variables for the frontend live in `frontend/configs/.env`
+(Vite is configured to read them from there). Only variables prefixed with
+`VITE_` are exposed to the browser code:
+
+```text
+VITE_BACKEND_URL=http://localhost:8000
+```
+
+The frontend calls the backend directly (no dev proxy), so `FRONT_URL` in the
+backend's `configs/.env` must match the address the frontend runs on.
+Restart `yarn dev` after changing either file.
## Testing
diff --git a/src/cache/rate_limit.py b/src/cache/rate_limit.py
index b508532..8b58e88 100644
--- a/src/cache/rate_limit.py
+++ b/src/cache/rate_limit.py
@@ -6,19 +6,19 @@ class RateLimit:
def __init__(self) -> None:
self.errors=Errors()
- async def rate_limit(self, ip:str)->None:
+ async def rate_limit(self, ip:str, action:str, limit:int=5, window:int=60)->None:
- key=f"action attempt {ip}"
+ key=f"rate_limit:{action}:{ip}"
attempts = await redis_client.incrby(key)
if attempts == 1:
- await redis_client.expire(key, 60)
+ await redis_client.expire(key, window, nx=True)
- if attempts>5:
- raise self.errors.rate_limit_error(detail="too many attempts", retry_after=60)
+ if attempts>limit:
+ raise self.errors.rate_limit_error(detail="too many attempts", retry_after=window)
- async def check_rate_limit(self, client_ip:str) -> None:
- await self.rate_limit(client_ip)
+ async def check_rate_limit(self, client_ip:str, action:str) -> None:
+ await self.rate_limit(client_ip, action)
rate_limiter=RateLimit()
\ No newline at end of file
diff --git a/src/database/auth/__init__.py b/src/database/auth/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/database/users/__init__.py b/src/database/users/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/errors/http_errors/__init__.py b/src/errors/http_errors/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/messaging/consumers/__init__.py b/src/messaging/consumers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/messaging/consumers/consumers.py b/src/messaging/consumers/consumers.py
index 6790af1..c1b7693 100644
--- a/src/messaging/consumers/consumers.py
+++ b/src/messaging/consumers/consumers.py
@@ -73,9 +73,10 @@ class ResetEmailConsumer:
message_id_ctx.set(str(uuid4()))
async with message.process(ignore_processed=True):
data = json.loads(message.body)
- daemon_logger.info(f"Обрабатываю: {data}, метка: {message.routing_key}")
+ daemon_logger.info(f"Обрабатываю reset для {data.get('email')}:, метка: {message.routing_key}")
try:
await self.daemon.send_email(data.get("email"), data.get("temp_password"))
+
except (
smtplib.SMTPServerDisconnected,
smtplib.SMTPConnectError,
diff --git a/src/messaging/producers/__init__.py b/src/messaging/producers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/models/configs_read/__init__.py b/src/models/configs_read/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/models/configs_read/env.py b/src/models/configs_read/env.py
index 25ecfe3..ca14a12 100644
--- a/src/models/configs_read/env.py
+++ b/src/models/configs_read/env.py
@@ -31,6 +31,7 @@ class Env(Base):
EMAIL_LOGIN:str
EMAIL_PASSWORD:str
+ FRONT_URL:str
PROD_MODE:bool
model_config=SettingsConfigDict(env_file="configs/.env", extra=None)
diff --git a/src/models/pydantic_models/__init__.py b/src/models/pydantic_models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/models/rabbitmq_models/__init__.py b/src/models/rabbitmq_models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/service/auth/__init__.py b/src/service/auth/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/service/auth/auth.py b/src/service/auth/auth.py
index 9a81b12..f1ad568 100644
--- a/src/service/auth/auth.py
+++ b/src/service/auth/auth.py
@@ -205,8 +205,6 @@ class CurrentUserService:
else:
raise self.error.not_found_error(detail="Refresh Token Not Found")
-
-
async def login(self, form_data_email:str, form_data_password:str, request:Request)->tuple[str, str]:
'''revoke all the old refresh tokens'''
user = await self._check(form_data_email, form_data_password)
diff --git a/src/service/auth/jwt.py b/src/service/auth/jwt.py
index a8dff5e..2e7af5b 100644
--- a/src/service/auth/jwt.py
+++ b/src/service/auth/jwt.py
@@ -1,4 +1,6 @@
import hashlib
+import secrets
+import string
from datetime import UTC, datetime, timedelta
from uuid import uuid4
@@ -22,6 +24,16 @@ class HashService:
def token_to_hash(self, token:str)->str:
return hashlib.sha256(token.encode("utf-8")).hexdigest()
+
+ async def generate_temp_password(self, length:int=16)->str:
+ SPECIAL = "$@#%!^&*()-_+=[]{}"
+ groups = [string.ascii_uppercase, string.ascii_lowercase, string.digits, SPECIAL]
+ chars = [secrets.choice(g) for g in groups] # по одному из каждой группы
+ pool = "".join(groups)
+ chars += [secrets.choice(pool) for _ in range(length - len(chars))]
+ secrets.SystemRandom().shuffle(chars) # чтобы спецсимвол не всегда стоял первым
+ return "".join(chars)
+
'''jwt'''
diff --git a/src/service/email/__init__.py b/src/service/email/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/service/users_crud/__init__.py b/src/service/users_crud/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/service/users_crud/users_crud.py b/src/service/users_crud/users_crud.py
index ccbea5f..d187bea 100644
--- a/src/service/users_crud/users_crud.py
+++ b/src/service/users_crud/users_crud.py
@@ -75,6 +75,12 @@ class CrudService:
if not user_entity:
raise self.errors.not_found_error(detail="User not found")
return UserOut.model_validate(user_entity)
+
+ async def reset_password(self, email:str)->str:
+ new_password=await self.hash_service.generate_temp_password()
+ await self.update_user(email, UserUpdate(plain_password=new_password)) # type: ignore[call-arg]
+
+ return new_password
async def crud_service()->CrudService:
return CrudService()
\ No newline at end of file
diff --git a/src/web/protected_routes/__init__.py b/src/web/protected_routes/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/web/protected_routes/auth_routes.py b/src/web/protected_routes/auth_routes.py
index f4784d5..64c4809 100644
--- a/src/web/protected_routes/auth_routes.py
+++ b/src/web/protected_routes/auth_routes.py
@@ -2,9 +2,11 @@ from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, Response
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from src.cache.rate_limit import rate_limiter
+from src.messaging.producers.producers import email_producer
from src.models.configs_read.env import env_settings
from src.models.pydantic_models.model import UserOut
from src.service.auth.auth import CurrentUserService, auth_service
+from src.service.users_crud.users_crud import CrudService, crud_service
router=APIRouter(prefix="/protected")
oauth2_schema=OAuth2PasswordBearer(tokenUrl="/protected/token", refreshUrl="/protected/refresh")
@@ -31,7 +33,7 @@ async def get_access_token(request: Request,
try:
access_token, refresh_token=await auth.login(form_data_email=form_data.username, form_data_password=form_data.password, request=request)
except HTTPException:
- await rate_limiter.rate_limit(client_ip)
+ await rate_limiter.rate_limit(client_ip, "login", 5, 60)
raise
response.set_cookie(
@@ -74,6 +76,22 @@ async def logout(response:Response,
response.delete_cookie("refresh_token")
return await auth.logout(refresh_token, access_token)
+@router.post("/reset")
+async def reset(request: Request,
+ email:str,
+ crud:CrudService=Depends(crud_service)
+ ):
+
+ client_ip = request.headers.get('x-forwarded-for', '').split(',')[0].strip() or (request.client.host if request.client else 'unknown')
+ await rate_limiter.rate_limit(client_ip, "reset", 3, 600)
+ try:
+ temp_password=await crud.reset_password(email)
+ await email_producer.send_reset_email(email, temp_password)
+ except HTTPException as exc:
+ if exc.status_code!=404:
+ raise
+ return {"detail":"New password was sent"}
+
@router.get("")
async def protected(current_user:UserOut=Depends(require_permissions()))->dict: