diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2072dc6..f4b5159 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,6 +67,34 @@ jobs: - name: Check code formatting run: cargo fmt --all -- --check + lint-frontend: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: apps/frontend/pnpm-lock.yaml + + - name: Install frontend dependencies + run: | + cd apps/frontend + pnpm install + + - name: Run frontend linter + run: | + cd apps/frontend + pnpm lint + test-frontend: runs-on: ubuntu-latest steps: diff --git a/Cargo.lock b/Cargo.lock index 1f96fe9..6966840 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3975,6 +3975,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "http", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -4713,6 +4727,7 @@ dependencies = [ "serde_json", "tokio", "tower", + "tower-http", "tracing", "tracing-subscriber", "utoipa", diff --git a/apps/api/Cargo.toml b/apps/api/Cargo.toml index 8e400da..593f5ec 100644 --- a/apps/api/Cargo.toml +++ b/apps/api/Cargo.toml @@ -27,4 +27,5 @@ once_cell = { version = "1.21.3" } argon2 = { version = "0.5.3", features = ["std"] } jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] } uuid = { version = "1.19.0", features = ["v4", "serde", "fast-rng"] } +tower-http = { version = "0.6.8", features = ["cors"] } diff --git a/apps/api/src/cmd/start_server.rs b/apps/api/src/cmd/start_server.rs index e49f5a8..c33cadf 100644 --- a/apps/api/src/cmd/start_server.rs +++ b/apps/api/src/cmd/start_server.rs @@ -88,8 +88,10 @@ pub async fn start_server() { // build the axum app and run the server... info!("Starting application..."); - let mut app: Router = - routes::get_root_router(Arc::new(get_app_state(&db_connection, &settings))); + let mut app: Router = routes::get_root_router( + Arc::new(get_app_state(&db_connection, &settings)), + Arc::new(settings.server.cors.clone()), + ); if settings.server.serve_openapi { info!("Enabling OpenAPI documentation endpoint at /openapi.json"); @@ -145,6 +147,7 @@ fn get_app_state( ) -> AppState { AppState { database_connection: db_connection.clone(), + config: Arc::new(settings.clone()), service: Arc::new(AppService { server_state: Arc::new(ServerStateService::new(db_connection.clone())), settings: Arc::new(SettingsService::new(db_connection.clone())), diff --git a/apps/api/src/configs.rs b/apps/api/src/configs.rs index 6c274d6..68b46d0 100644 --- a/apps/api/src/configs.rs +++ b/apps/api/src/configs.rs @@ -11,6 +11,8 @@ use tracing::{debug, error}; pub trait FromConfig: Sized { fn from_config(config: &Config) -> Result; fn validate(&self) -> Result<(), String>; + #[cfg(test)] + fn mock() -> Self; } #[derive(Debug, Clone)] @@ -40,6 +42,16 @@ impl FromConfig for ProgramSettings { self.auth.validate()?; Ok(()) } + + #[cfg(test)] + fn mock() -> Self { + ProgramSettings { + logging: logging::LoggingSettings::mock(), + database: database::DatabaseSettings::mock(), + server: server::ServerSettings::mock(), + auth: auth::AuthSettings::mock(), + } + } } pub fn get_program_settings() -> ProgramSettings { diff --git a/apps/api/src/configs/auth.rs b/apps/api/src/configs/auth.rs index 4041092..c115241 100644 --- a/apps/api/src/configs/auth.rs +++ b/apps/api/src/configs/auth.rs @@ -48,4 +48,13 @@ impl FromConfig for AuthSettings { fn validate(&self) -> Result<(), String> { Ok(()) } + + #[cfg(test)] + fn mock() -> Self { + AuthSettings { + jwt_secret: Some("mock_jwt_secret".to_string()), + default_admin_username: Some("admin".to_string()), + default_admin_password: Some("password".to_string()), + } + } } diff --git a/apps/api/src/configs/database.rs b/apps/api/src/configs/database.rs index 25d9a78..596bfa3 100644 --- a/apps/api/src/configs/database.rs +++ b/apps/api/src/configs/database.rs @@ -50,4 +50,13 @@ impl FromConfig for DatabaseSettings { fn validate(&self) -> Result<(), String> { Ok(()) } + + #[cfg(test)] + fn mock() -> Self { + DatabaseSettings { + url: "sqlite::memory:".to_string(), + max_connections: 5, + migrate_on_startup: true, + } + } } diff --git a/apps/api/src/configs/key.rs b/apps/api/src/configs/key.rs index 0bc3baf..dae73a4 100644 --- a/apps/api/src/configs/key.rs +++ b/apps/api/src/configs/key.rs @@ -4,6 +4,8 @@ pub(crate) const LOGGING_UTC_KEY: &str = "LOGGING.UTC"; pub(crate) const SERVER_ADDRESS_KEY: &str = "SERVER.ADDRESS"; pub(crate) const SERVER_PORT_KEY: &str = "SERVER.PORT"; pub(crate) const SERVER_SERVE_OPENAPI_KEY: &str = "SERVER.SERVE_OPENAPI"; +pub(crate) const SERVER_CORS_ALLOWED_ORIGINS_KEY: &str = "SERVER.CORS.ALLOWED_ORIGINS"; +pub(crate) const SERVER_COOKIES_SECURE_KEY: &str = "SERVER.COOKIES.SECURE"; // pub(crate) const DATABASE_URL_KEY: &str = "DATABASE.URL"; pub(crate) const DATABASE_MAX_CONNECTIONS_KEY: &str = "DATABASE.MAX_CONNECTIONS"; diff --git a/apps/api/src/configs/logging.rs b/apps/api/src/configs/logging.rs index 1aa47b9..55bb578 100644 --- a/apps/api/src/configs/logging.rs +++ b/apps/api/src/configs/logging.rs @@ -49,4 +49,12 @@ impl FromConfig for LoggingSettings { fn validate(&self) -> Result<(), String> { Ok(()) } + + #[cfg(test)] + fn mock() -> Self { + LoggingSettings { + level: Level::INFO, + utc: false, + } + } } diff --git a/apps/api/src/configs/server.rs b/apps/api/src/configs/server.rs index a79ce14..cb18b94 100644 --- a/apps/api/src/configs/server.rs +++ b/apps/api/src/configs/server.rs @@ -3,7 +3,9 @@ use std::net::IpAddr; use config::{Config, ConfigError}; use tracing::warn; -use crate::configs::key::SERVER_SERVE_OPENAPI_KEY; +use crate::configs::key::{ + SERVER_COOKIES_SECURE_KEY, SERVER_CORS_ALLOWED_ORIGINS_KEY, SERVER_SERVE_OPENAPI_KEY, +}; use super::{ FromConfig, @@ -15,6 +17,18 @@ pub struct ServerSettings { pub address: IpAddr, pub port: u16, pub serve_openapi: bool, + pub cors: CORSSettings, + pub cookies: CookiesSettings, +} + +#[derive(Debug, Clone)] +pub struct CORSSettings { + pub allowed_origins: Vec, +} + +#[derive(Debug, Clone)] +pub struct CookiesSettings { + pub secure: bool, } impl FromConfig for ServerSettings { @@ -57,6 +71,42 @@ impl FromConfig for ServerSettings { ); DEFAULT_SERVE_OPENAPI }), + + cors: CORSSettings { + allowed_origins: _config + .get_array(SERVER_CORS_ALLOWED_ORIGINS_KEY) + .unwrap_or_else(|_| vec![]) + .into_iter() + .filter_map(|val| match val.into_string() { + Ok(s) => Some(s), + Err(e) => { + warn!( + "Invalid origin in {} configuration: {}", + SERVER_CORS_ALLOWED_ORIGINS_KEY, e + ); + None + } + }) + .collect(), + }, + + cookies: CookiesSettings { + secure: _config + .get_bool(SERVER_COOKIES_SECURE_KEY) + .inspect(|is_secure| { + if !*is_secure { + warn!("Cookie 'secure' flag is disabled; this is not recommended in production environments."); + } + }) + .unwrap_or_else(|err| { + const DEFAULT_COOKIES_SECURE: bool = true; + warn!( + "{} not set or invalid in configuration, defaulting to {}. Error: {}", + SERVER_COOKIES_SECURE_KEY, DEFAULT_COOKIES_SECURE, err + ); + DEFAULT_COOKIES_SECURE + }), + }, }) } @@ -67,4 +117,17 @@ impl FromConfig for ServerSettings { } Ok(()) } + + #[cfg(test)] + fn mock() -> Self { + ServerSettings { + address: "0.0.0.0".parse().unwrap(), + port: 8080, + serve_openapi: false, + cors: CORSSettings { + allowed_origins: vec![], + }, + cookies: CookiesSettings { secure: true }, + } + } } diff --git a/apps/api/src/middlewares.rs b/apps/api/src/middlewares.rs index 69935b1..8799c1d 100644 --- a/apps/api/src/middlewares.rs +++ b/apps/api/src/middlewares.rs @@ -6,25 +6,55 @@ use std::{sync::Arc, time::Duration}; use axum::{ BoxError, Router, error_handling::HandleErrorLayer, - http::{Method, StatusCode, Uri}, + http::{HeaderValue, Method, StatusCode, Uri}, }; use tower::{ServiceBuilder, timeout::TimeoutLayer}; +use tower_http::cors::{AllowHeaders, AllowOrigin, CorsLayer}; use tracing::warn; -use crate::routes::AppState; +use crate::{configs::server::CORSSettings, routes::AppState}; pub const TIMEOUT_DURATION_SECS: u64 = 30; -pub fn apply_root_middleware(router: Router, _state: Arc) -> Router { +pub fn apply_root_middleware( + router: Router, + _state: Arc, + cors_settings: Arc, +) -> Router { let timeout_layer = TimeoutLayer::new(Duration::from_secs(TIMEOUT_DURATION_SECS)); let service_builder = ServiceBuilder::new() .layer(HandleErrorLayer::new(handle_timeout_error)) - .layer(timeout_layer); + .layer(timeout_layer) + .layer(get_cors_layer(cors_settings)); router.layer(service_builder) } +pub fn get_cors_layer(cors_settings: Arc) -> CorsLayer { + let mut cors_layer = CorsLayer::new() + .allow_credentials(true) + .allow_headers(AllowHeaders::mirror_request()); + + let allowed_origins = &cors_settings.allowed_origins; + if allowed_origins.contains(&"*".to_string()) { + cors_layer = cors_layer.allow_origin(AllowOrigin::mirror_request()); + warn!( + "Wildcard origin is found in allowed origins. CORS is configured to allow requests from any origin. Only use this setting in development or if you understand the security implications." + ); + } else { + for origin in allowed_origins { + if let Ok(header_value) = HeaderValue::from_str(origin) { + cors_layer = cors_layer.allow_origin(AllowOrigin::exact(header_value)); + } else { + warn!("Invalid CORS origin: {}", origin); + } + } + } + + cors_layer +} + pub async fn handle_timeout_error( method: Method, uri: Uri, diff --git a/apps/api/src/middlewares/require_auth.rs b/apps/api/src/middlewares/require_auth.rs index b504210..5cf3a6f 100644 --- a/apps/api/src/middlewares/require_auth.rs +++ b/apps/api/src/middlewares/require_auth.rs @@ -7,6 +7,7 @@ use axum::{ response::Response, }; use axum_extra::extract::cookie::CookieJar; +use tracing::debug; use uuid::Uuid; use crate::{ @@ -25,6 +26,7 @@ pub async fn require_auth( let token = if let Some(cookie) = cookies.get(JWT_COOKIE_NAME) { cookie.value().to_string() } else { + debug!("No JWT cookie found. cookies: {:?}", cookies); return handle_unauthenticated().await; }; diff --git a/apps/api/src/routes.rs b/apps/api/src/routes.rs index 41cc73b..cffc71b 100644 --- a/apps/api/src/routes.rs +++ b/apps/api/src/routes.rs @@ -9,6 +9,7 @@ use axum::{Extension, Router}; use migration::sea_orm::DatabaseConnection; use crate::{ + configs::{ProgramSettings, server::CORSSettings}, middlewares, services::{ auth::{ @@ -22,10 +23,9 @@ use crate::{ #[derive(Clone)] pub struct AppState { - #[allow(dead_code)] pub database_connection: Arc, - #[allow(dead_code)] pub service: Arc, + pub config: Arc, } pub type ServiceState = Arc; @@ -46,7 +46,10 @@ pub struct AppService { pub server_state: ServiceState, } -pub fn get_root_router(state: impl Into>) -> Router { +pub fn get_root_router( + state: impl Into>, + cors_settings: Arc, +) -> Router { let mut router = Router::new(); let state = state.into(); @@ -54,7 +57,7 @@ pub fn get_root_router(state: impl Into>) -> Router { .nest("/api", api::get_api_router(state.clone())) .merge(view::get_view_router()); - router = middlewares::apply_root_middleware(router, state.clone()); + router = middlewares::apply_root_middleware(router, state.clone(), cors_settings); router = router.layer(Extension(state.clone())); diff --git a/apps/api/src/routes/api/auth/login.rs b/apps/api/src/routes/api/auth/login.rs index 06f16c4..1d4519f 100644 --- a/apps/api/src/routes/api/auth/login.rs +++ b/apps/api/src/routes/api/auth/login.rs @@ -11,7 +11,10 @@ use serde::{Deserialize, Serialize}; use serde_json::{Value, from_value}; use tracing::{error, warn}; -use crate::routes::{AppState, api::openapi::tag::AUTH_TAG}; +use crate::{ + helpers::constants::JWT_COOKIE_NAME, + routes::{AppState, api::openapi::tag::AUTH_TAG}, +}; /// Login request payload #[derive(Serialize, Deserialize, utoipa::ToSchema)] @@ -81,9 +84,15 @@ pub async fn login(State(state): State>, Json(payload): Json) -> Router { Router::new() - // - // + .nest("/user", user::get_user_router(state.clone())) .layer(axum::middleware::from_fn_with_state( state.clone(), require_auth, diff --git a/apps/api/src/routes/api/restricted/user.rs b/apps/api/src/routes/api/restricted/user.rs new file mode 100644 index 0000000..77e1167 --- /dev/null +++ b/apps/api/src/routes/api/restricted/user.rs @@ -0,0 +1,13 @@ +pub mod me; + +use std::sync::Arc; + +use axum::Router; + +use crate::routes::AppState; + +pub fn get_user_router(state: Arc) -> Router { + Router::new() + .route("/me", axum::routing::get(me::get_user_info)) + .with_state(state) +} diff --git a/apps/api/src/routes/api/restricted/user/me.rs b/apps/api/src/routes/api/restricted/user/me.rs new file mode 100644 index 0000000..a95d5de --- /dev/null +++ b/apps/api/src/routes/api/restricted/user/me.rs @@ -0,0 +1,64 @@ +use std::sync::Arc; + +use axum::{ + Extension, Json, + extract::State, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use serde::{Deserialize, Serialize}; +use tracing::error; + +use crate::{ + middlewares::request_info::RequestInfo, + routes::{AppState, api::openapi::tag::USER_TAG}, +}; + +/// System health information +#[derive(Serialize, Deserialize, utoipa::ToSchema)] +pub struct UserInfo { + /// User ID + pub id: uuid::Uuid, + /// Username + pub username: String, +} + +/// Get current user information +/// +/// Returns the information of the currently authenticated user. +#[utoipa::path( + get, + path = "/api/user/me", + responses( + (status = 200, description = "User information retrieved successfully", body = UserInfo), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error"), + ), + tag = USER_TAG, + )] +pub async fn get_user_info( + State(app_state): State>, + request_info: Extension>, +) -> Response { + let user_id = match request_info.user_id { + Some(id) => id, + None => { + error!("User ID not found in request info"); + return (StatusCode::UNAUTHORIZED).into_response(); + } + }; + + match app_state.service.user.get_user_by_id(user_id, None).await { + Ok(user) => { + let user_info = UserInfo { + id: user.id, + username: user.username, + }; + (StatusCode::OK, Json(user_info)).into_response() + } + Err(err) => { + error!("Error fetching user info: {}", err); + (StatusCode::INTERNAL_SERVER_ERROR).into_response() + } + } +} diff --git a/apps/api/src/services/auth/authentication.rs b/apps/api/src/services/auth/authentication.rs index d471e73..9fd459b 100644 --- a/apps/api/src/services/auth/authentication.rs +++ b/apps/api/src/services/auth/authentication.rs @@ -5,7 +5,7 @@ use std::{collections::HashSet, sync::Arc}; use argon2::password_hash::{SaltString, rand_core::OsRng}; use jsonwebtoken::{ DecodingKey, EncodingKey, Header, Validation, decode, encode, - errors::ErrorKind::{ExpiredSignature, InvalidSubject, InvalidToken}, + errors::ErrorKind::{ExpiredSignature, InvalidSignature, InvalidSubject, InvalidToken}, }; use sea_orm::prelude::Uuid; use serde::{Deserialize, Serialize}; @@ -124,7 +124,7 @@ impl AuthenticationService for AuthenticationServiceImpl { match decode::(token, &decoding_key, &validation) { Ok(data) => Ok(Some(data.claims)), Err(err) => match *err.kind() { - InvalidToken | InvalidSubject | ExpiredSignature => Ok(None), + InvalidToken | InvalidSubject | ExpiredSignature | InvalidSignature => Ok(None), _ => Err(ServiceError::InternalError(format!( "JWT validation error: {}", err diff --git a/apps/api/src/services/auth/user.rs b/apps/api/src/services/auth/user.rs index 03703ac..691c0e1 100644 --- a/apps/api/src/services/auth/user.rs +++ b/apps/api/src/services/auth/user.rs @@ -51,7 +51,6 @@ pub trait UserService: Send + Sync { pub struct User { pub id: Uuid, - #[allow(dead_code)] // TODO: remove when used pub username: String, #[allow(dead_code)] // TODO: remove when used pub is_admin: bool, diff --git a/apps/api/src/services/settings.rs b/apps/api/src/services/settings.rs index aa48f16..213fc29 100644 --- a/apps/api/src/services/settings.rs +++ b/apps/api/src/services/settings.rs @@ -11,14 +11,11 @@ use crate::errors::service_error::ServiceError; #[async_trait::async_trait] pub trait SettingsStore: Send + Sync { - #[allow(dead_code)] // TODO: remove when used async fn get_setting(&self, key: &str) -> Result; - #[allow(dead_code)] // TODO: remove when used async fn set_setting(&self, key: &str, value: String) -> Result<(), ServiceError>; } pub struct SettingsService { - #[allow(dead_code)] // TODO: remove when used connection: Arc, } diff --git a/apps/api/swagger.json b/apps/api/swagger.json index dc65ac2..cf7fddc 100644 --- a/apps/api/swagger.json +++ b/apps/api/swagger.json @@ -105,6 +105,34 @@ } } } + }, + "/api/user/me": { + "get": { + "tags": [ + "User" + ], + "summary": "Get current user information", + "description": "Returns the information of the currently authenticated user.", + "operationId": "get_user_info", + "responses": { + "200": { + "description": "User information retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserInfo" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } } }, "components": { @@ -183,6 +211,25 @@ "type": "string" } } + }, + "UserInfo": { + "type": "object", + "description": "System health information", + "required": [ + "id", + "username" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "User ID" + }, + "username": { + "type": "string", + "description": "Username" + } + } } } }, @@ -194,6 +241,10 @@ { "name": "Authentication", "description": "Authentication API" + }, + { + "name": "User", + "description": "User management API" } ] } \ No newline at end of file diff --git a/apps/frontend/app/app.css b/apps/frontend/app/app.css index 99345d8..107983d 100644 --- a/apps/frontend/app/app.css +++ b/apps/frontend/app/app.css @@ -1,15 +1,9 @@ -@import "tailwindcss"; +@import 'tailwindcss'; @theme { - --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; } html, body { - @apply bg-white dark:bg-gray-950; - - @media (prefers-color-scheme: dark) { - color-scheme: dark; - } } diff --git a/apps/frontend/app/components/Form/Button.tsx b/apps/frontend/app/components/Form/Button.tsx new file mode 100644 index 0000000..55890d7 --- /dev/null +++ b/apps/frontend/app/components/Form/Button.tsx @@ -0,0 +1,46 @@ +import { Button, type ButtonProps } from '@radix-ui/themes'; +import { LoaderCircle } from 'lucide-react'; + +export type SubmitButtonProps = { + loading?: boolean; + label?: + | { + default?: string; + loading?: string; + } + | string; +} & React.ButtonHTMLAttributes & + ButtonProps; + +export function SubmitButton({ loading, label, ...props }: SubmitButtonProps) { + return ( + + ); +} + +export function ResetButton(props: React.ButtonHTMLAttributes) { + return ( + + ); +} diff --git a/apps/frontend/app/components/Form/TextField.tsx b/apps/frontend/app/components/Form/TextField.tsx new file mode 100644 index 0000000..c21f25c --- /dev/null +++ b/apps/frontend/app/components/Form/TextField.tsx @@ -0,0 +1,103 @@ +import type { AnyFieldMeta } from '@tanstack/react-form'; +import { LucideEye, LucideEyeClosed } from 'lucide-react'; +import { useCallback, useId, useState } from 'react'; +import { InfoIcon, type InfoIconProps } from '../info'; +import { Text } from '@radix-ui/themes'; + +export type TextFieldProps = { + label?: string; + value?: string; + onChange?: (e: React.ChangeEvent) => void; + labelProps?: React.LabelHTMLAttributes; + labelDivProps?: React.HTMLAttributes; + infoIconProps?: InfoIconProps; +} & React.InputHTMLAttributes & { + type?: 'password'; + showPasswordToggle?: boolean; + }; + +export function TextField({ label, value, onChange, labelProps, labelDivProps, showPasswordToggle, infoIconProps, ...rest }: TextFieldProps) { + const id = useId(); + const [isPasswordVisible, setIsPasswordVisible] = useState(false); + const handlePasswordVisibilitySet = useCallback( + (e: React.MouseEvent | React.TouchEvent, visible: boolean) => { + if (rest.type !== 'password') return; + e.preventDefault(); + setIsPasswordVisible(() => visible); + }, + [rest.type] + ); + + return ( + + ); +} + +export type TextFieldErrorMessageProps = AnyFieldMeta & { + errorMessage?: string; +}; + +export function TextFieldErrorMessage({ isValid, errors, errorMessage }: TextFieldErrorMessageProps) { + return ( + !isValid && ( +
+ {errorMessage ?? errors?.reduce((msg, err) => msg + err.message + ' ', '')} +
+ ) + ); +} diff --git a/apps/frontend/app/components/home/TablePlaceholder.tsx b/apps/frontend/app/components/home/TablePlaceholder.tsx new file mode 100644 index 0000000..50645b4 --- /dev/null +++ b/apps/frontend/app/components/home/TablePlaceholder.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Flex, Text, Button, Separator, Box, Badge } from '@radix-ui/themes'; + +export default function TablePlaceholder() { + return ( + + + Proxy Hosts + + + + {[1, 2, 3].map((i) => ( + + + + {`host-${i}.example.com`} + + + {`http://10.0.0.${i}:8080`} + + + Online + + ))} + + ); +} diff --git a/apps/frontend/app/components/info.tsx b/apps/frontend/app/components/info.tsx new file mode 100644 index 0000000..cb277f0 --- /dev/null +++ b/apps/frontend/app/components/info.tsx @@ -0,0 +1,59 @@ +import { Box } from '@radix-ui/themes'; +import { Info, type LucideProps } from 'lucide-react'; +import { Tooltip } from 'radix-ui'; +import type { PropsWithChildren } from 'react'; + +export type InfoIconProps = PropsWithChildren< + { + tooltipContainerProps?: Omit, 'children'>; + } & Omit & + React.RefAttributes +>; + +export function InfoIcon({ tooltipContainerProps, children, ...iconProps }: InfoIconProps) { + return ( + + + + + + + {children} + + + + + ); +} + +export function TooltipContentContainer({ children, ...props }: React.HTMLAttributes) { + return ( + + {children} + + ); +} diff --git a/apps/frontend/app/components/layout/SidebarContent.tsx b/apps/frontend/app/components/layout/SidebarContent.tsx new file mode 100644 index 0000000..c5f7ae8 --- /dev/null +++ b/apps/frontend/app/components/layout/SidebarContent.tsx @@ -0,0 +1,89 @@ +import type React from 'react'; +import { Box, Button, Flex, Heading, Separator, Text } from '@radix-ui/themes'; +import type { NavItem } from './types'; +import { Home, Globe, ArrowRight, Lock, Settings, User } from 'lucide-react'; +import { useLayout } from '../../providers/LayoutProvider'; + +const navItems: { label: NavItem; icon: React.ReactNode }[] = [ + { label: 'Dashboard', icon: }, + { label: 'Proxy Hosts', icon: }, + { label: 'Redirection', icon: }, + { label: 'SSL', icon: }, + { label: 'Settings', icon: }, + { label: 'Profile', icon: }, +] as const; + +export function SidebarContent() { + const { activeTab, setActiveTab, setIsMobileMenuOpen } = useLayout(); + + return ( + + + + Y + + + YANPM + + + + + {navItems.map((item) => ( + + ))} + + + + + + + + + Admin User + + + admin@example.com + + + + + + ); +} + +export default SidebarContent; diff --git a/apps/frontend/app/components/layout/types.ts b/apps/frontend/app/components/layout/types.ts new file mode 100644 index 0000000..c2159ce --- /dev/null +++ b/apps/frontend/app/components/layout/types.ts @@ -0,0 +1 @@ +export type NavItem = 'Dashboard' | 'Proxy Hosts' | 'Redirection' | 'SSL' | 'Settings' | 'Profile'; diff --git a/apps/frontend/app/components/theme.tsx b/apps/frontend/app/components/theme.tsx new file mode 100644 index 0000000..4426a55 --- /dev/null +++ b/apps/frontend/app/components/theme.tsx @@ -0,0 +1,16 @@ +import type React from 'react'; +import { Theme } from '@radix-ui/themes'; + +export type AppThemeProps = { + children: React.ReactNode; +}; + +export function AppTheme({ children }: AppThemeProps) { + return ( + + {children} + + ); +} + +export default AppTheme; diff --git a/apps/frontend/app/empty-toastify.css b/apps/frontend/app/empty-toastify.css new file mode 100644 index 0000000..251126f --- /dev/null +++ b/apps/frontend/app/empty-toastify.css @@ -0,0 +1 @@ +/* intentionally empty: used to stub react-toastify CSS in production builds */ diff --git a/apps/frontend/app/generated/api-client/api-client.ts b/apps/frontend/app/generated/api-client/api-client.ts index 5ebf513..0b47c95 100644 --- a/apps/frontend/app/generated/api-client/api-client.ts +++ b/apps/frontend/app/generated/api-client/api-client.ts @@ -9,6 +9,7 @@ export namespace Schemas { version: string; }; export type LoginRequest = { password: string; username: string }; + export type UserInfo = { id: string; username: string }; // } @@ -41,6 +42,13 @@ export namespace Endpoints { parameters: never; responses: { 200: Schemas.HealthInfo; 404: unknown }; }; + export type get_Get_user_info = { + method: "GET"; + path: "/api/user/me"; + requestFormat: "json"; + parameters: never; + responses: { 200: Schemas.UserInfo; 401: unknown; 500: unknown }; + }; // } @@ -53,6 +61,7 @@ export type EndpointByMethod = { }; get: { "/api/health/info": Endpoints.get_Get_health_info; + "/api/user/me": Endpoints.get_Get_user_info; }; }; diff --git a/apps/frontend/app/hooks/ResponseHelper.tsx b/apps/frontend/app/hooks/ResponseHelper.tsx new file mode 100644 index 0000000..0b48c02 --- /dev/null +++ b/apps/frontend/app/hooks/ResponseHelper.tsx @@ -0,0 +1,73 @@ +import { AxiosError } from 'axios'; +import { useLocation, useNavigate } from 'react-router'; +import { SearchParamKeys } from '../lib/constants'; +import { useQueryMessage } from './useQueryMessage'; +import { QueryMessageCode, QueryMessageType } from '../lib/QueryMessages'; +import { useCallback } from 'react'; +import { displayForbiddenErrorToast, displayNetworkErrorToast, displayUnexpectedErrorToast } from '../lib/toasts'; + +export enum ResponseErrorToastId { + NetworkError = 'network-error', +} + +export type DefaultResponseErrorHandlerOptions = { + disableUnauthorizedHandling?: boolean; + disableHandleUnexpectedErrors?: boolean; + disableIgnoreCanceledRequests?: boolean; +}; + +/** + * + * @param err error value + * @returns {boolean} true if the error was handled, false otherwise + */ + +export function useResponseErrorHandler(): { + defaultResponseErrorHandler: typeof defaultResponseErrorHandler; +} { + const navigate = useNavigate(); + const location = useLocation(); + const { toSearchParamQueryMessage } = useQueryMessage(); + + const defaultResponseErrorHandler = useCallback( + (err: unknown, options?: DefaultResponseErrorHandlerOptions): boolean => { + if (!(err instanceof AxiosError) && !options?.disableHandleUnexpectedErrors) { + displayUnexpectedErrorToast(); + return true; + } + + if (!(err instanceof AxiosError)) return false; + + if (err.message === 'canceled') { + // request was aborted, ignore but return true to indicate it was handled + return !options?.disableIgnoreCanceledRequests; + } + + if (err.message === 'Network Error') { + displayNetworkErrorToast(); + return true; + } + + // handle 401 Unauthorized globally + if (err.status === 401 && !options?.disableUnauthorizedHandling) { + // store current path for redirect after login + const currentPath = location.pathname + location.search; + const searchParam = new URLSearchParams(); + searchParam.set(SearchParamKeys.Redirect, currentPath); + searchParam.set(SearchParamKeys.Message, toSearchParamQueryMessage(QueryMessageCode.SessionExpired, QueryMessageType.Info)); + navigate(`/login?${searchParam.toString()}`); + return true; + } + + if (err.status === 403) { + displayForbiddenErrorToast(); + return true; + } + + return false; + }, + [location, navigate, toSearchParamQueryMessage] + ); + + return { defaultResponseErrorHandler }; +} diff --git a/apps/frontend/app/hooks/ensureLoggedIn.tsx b/apps/frontend/app/hooks/ensureLoggedIn.tsx new file mode 100644 index 0000000..e23a4ab --- /dev/null +++ b/apps/frontend/app/hooks/ensureLoggedIn.tsx @@ -0,0 +1,48 @@ +import { useEffect } from 'react'; +import { useNavigate } from 'react-router'; +import { useAuth } from '../providers/AuthProvider'; +import { useApi } from '../providers/ApiProvider'; +import { useQuery } from '@tanstack/react-query'; +import { useResponseErrorHandler } from './ResponseHelper'; + +export type EnsureLoggedInResult = { + checking: boolean; + loggedIn: boolean; +}; + +export function useEnsureLoggedIn(): EnsureLoggedInResult { + const { user, setUser } = useAuth(); + const navigate = useNavigate(); + const { tanstackApiClient } = useApi(); + const { defaultResponseErrorHandler } = useResponseErrorHandler(); + + const { queryOptions: currentUserQuery } = tanstackApiClient.get('/api/user/me'); + const { isFetched, isPending } = useQuery({ + ...currentUserQuery, + queryFn: async (...args) => { + try { + const data = await currentUserQuery.queryFn!(...args); + setUser({ + id: data.id, + name: data.username, + }); + return data; + } catch (error) { + if (defaultResponseErrorHandler(error)) return {} as never; + throw error; + } + }, + }); + + useEffect(() => { + if (user) { + navigate('/', { replace: true }); + return; + } + }, [user, setUser, navigate]); + + return { + checking: isPending, + loggedIn: isFetched && !!user, + }; +} diff --git a/apps/frontend/app/hooks/useQueryMessage.tsx b/apps/frontend/app/hooks/useQueryMessage.tsx new file mode 100644 index 0000000..e9c3e5a --- /dev/null +++ b/apps/frontend/app/hooks/useQueryMessage.tsx @@ -0,0 +1,111 @@ +import { useCallback, useEffect, useRef, type ReactNode } from 'react'; +import { useLocation, useSearchParams } from 'react-router'; +import { toast } from 'react-toastify/unstyled'; +import { SearchParamKeys } from '../lib/constants'; +import { CODE_TO_MESSAGE_MAP, QueryMessageCode, QueryMessageType } from '../lib/QueryMessages'; + +type QueryMessageString = `${QueryMessageCode}__${QueryMessageType}`; + +export type QueryMessage = { + type: QueryMessageType; + code: QueryMessageCode; + message: ReactNode; +}; + +export type UseQueryMessageOptions = { + displayMessages?: boolean; +}; + +export type UseQueryMessageReturn = { + setQueryMessage: (messageCode: QueryMessageCode, messageType: QueryMessageType) => void; + clearQueryMessage: () => void; + toSearchParamQueryMessage: (message: QueryMessageCode, type: QueryMessageType) => QueryMessageString; +}; + +export function useQueryMessage( + { displayMessages }: UseQueryMessageOptions = { + displayMessages: true, + } +): UseQueryMessageReturn { + const location = useLocation(); + const [searchParams, setSearchParams] = useSearchParams(); + const messageStr = useRef(null); + + useEffect(() => { + // Reset messageStr when location changes to allow re-displaying the same message on navigation + messageStr.current = null; + }, [location.pathname]); + + useEffect(() => { + const queryMessageStr = searchParams.get(SearchParamKeys.Message); + if (!(queryMessageStr && queryMessageStr !== messageStr.current)) return; + const [queryMessage, queryMessageString] = toQueryMessage(queryMessageStr) ?? [null, null]; + if (!queryMessage) return; + + messageStr.current = queryMessageString; + if (displayMessages) { + toast[queryMessage.type](queryMessage.message, { + position: 'top-center', + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: false, + progress: undefined, + theme: 'colored', + toastId: 'login-route-info-message', + }); + } + }, [displayMessages, searchParams]); + + const setQueryMessage = useCallback( + (messageCode: QueryMessageCode, messageType: QueryMessageType) => { + const queryMessageString: QueryMessageString = `${messageCode}__${messageType}`; + messageStr.current = queryMessageString; + setSearchParams((prev) => { + prev.set(SearchParamKeys.Message, queryMessageString); + return prev; + }); + }, + [setSearchParams] + ); + + const clearQueryMessage = useCallback(() => { + messageStr.current = null; + setSearchParams((prev) => { + prev.delete(SearchParamKeys.Message); + return prev; + }); + }, [setSearchParams]); + + const toSearchParamQueryMessage = useCallback((message: QueryMessageCode, type: QueryMessageType): QueryMessageString => { + return `${message}__${type}`; + }, []); + + return { + setQueryMessage, + clearQueryMessage, + toSearchParamQueryMessage, + }; +} + +function isValidQueryMessageCode(code: string): code is QueryMessageCode { + return Object.values(QueryMessageCode).includes(code as QueryMessageCode); +} + +function isValidQueryMessageType(type: string): type is QueryMessageType { + return Object.values(QueryMessageType).includes(type as QueryMessageType); +} + +function toQueryMessage(value: string): [QueryMessage, QueryMessageString] | null { + const [code, type] = value.split('__'); + if (!isValidQueryMessageCode(code) || !isValidQueryMessageType(type)) return null; + return [ + { + code: code, + type: type, + message: CODE_TO_MESSAGE_MAP[code], + }, + `${code}__${type}`, + ]; +} diff --git a/apps/frontend/app/lib/QueryMessages.tsx b/apps/frontend/app/lib/QueryMessages.tsx new file mode 100644 index 0000000..85dff06 --- /dev/null +++ b/apps/frontend/app/lib/QueryMessages.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react'; + +export enum QueryMessageType { + Info = 'info', + Success = 'success', + Warning = 'warning', + Error = 'error', +} + +export enum QueryMessageCode { + SessionExpired = 'SESSION_EXPIRED', + InitializationRequired = 'INITIALIZATION_REQUIRED', + InitializationSuccessful = 'INITIALIZATION_SUCCESSFUL', +} + +export const CODE_TO_MESSAGE_MAP: Record = { + [QueryMessageCode.SessionExpired]: 'Your session has expired. Please log in again.', + [QueryMessageCode.InitializationRequired]: 'The application requires initialization. Please follow the setup instructions.', + [QueryMessageCode.InitializationSuccessful]: 'Initialization successful. Please log in.', +} as const; diff --git a/apps/frontend/app/lib/api.ts b/apps/frontend/app/lib/api.ts index 97e72ad..718091b 100644 --- a/apps/frontend/app/lib/api.ts +++ b/apps/frontend/app/lib/api.ts @@ -65,7 +65,34 @@ function axiosResponseToFetchResponse(response: AxiosResponse): Response { } }); - return new Response(response.data, { + // Normalize Axios response.data to a Fetch-compatible BodyInit + let body: BodyInit | null = null; + const data = response.data; + + if (data == null) { + body = null; + } else if ( + typeof data === 'string' || + data instanceof Blob || + data instanceof ArrayBuffer || + ArrayBuffer.isView(data) || + data instanceof FormData || + data instanceof URLSearchParams + ) { + body = data as BodyInit; + } else { + try { + body = JSON.stringify(data); + if (!headers.has('content-type')) { + headers.set('content-type', 'application/json;charset=utf-8'); + } + } catch { + console.warn('Failed to stringify response data as JSON, falling back to string conversion.'); + body = String(data); + } + } + + return new Response(body, { status: response.status, statusText: response.statusText, headers: headers, diff --git a/apps/frontend/app/lib/constants.ts b/apps/frontend/app/lib/constants.ts new file mode 100644 index 0000000..aa17dc8 --- /dev/null +++ b/apps/frontend/app/lib/constants.ts @@ -0,0 +1,4 @@ +export enum SearchParamKeys { + Redirect = 'redirect', + Message = 'message', +} diff --git a/apps/frontend/app/lib/toasts.tsx b/apps/frontend/app/lib/toasts.tsx new file mode 100644 index 0000000..8d2b577 --- /dev/null +++ b/apps/frontend/app/lib/toasts.tsx @@ -0,0 +1,64 @@ +import { toast, type ToastOptions } from 'react-toastify/unstyled'; +import { Text } from '@radix-ui/themes'; +import { ResponseErrorToastId } from '../hooks/ResponseHelper'; + +export const displayUnexpectedErrorToast = (options: ToastOptions = {}) => { + toast.error( +
+ Unexpected Error: +
An unexpected error occurred. Please try again later. +
, + { + position: 'top-center', + autoClose: false, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: false, + progress: undefined, + theme: 'colored', + ...options, + } + ); +}; + +export const displayNetworkErrorToast = (options: ToastOptions = {}) => { + toast.error( +
+ Network Error: +
Unable to reach the server. Please check your internet connection and try again. +
, + { + toastId: ResponseErrorToastId.NetworkError, + position: 'top-center', + autoClose: false, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: false, + progress: undefined, + theme: 'colored', + ...options, + } + ); +}; + +export const displayForbiddenErrorToast = (options: ToastOptions = {}) => { + toast.error( +
+ Forbidden: +
You do not have permission to perform this action. +
, + { + position: 'top-center', + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: false, + progress: undefined, + theme: 'colored', + ...options, + } + ); +}; diff --git a/apps/frontend/app/providers/ApiHealthProvider.tsx b/apps/frontend/app/providers/ApiHealthProvider.tsx new file mode 100644 index 0000000..225735c --- /dev/null +++ b/apps/frontend/app/providers/ApiHealthProvider.tsx @@ -0,0 +1,56 @@ +import { useNavigate } from 'react-router'; +import { useQuery } from '@tanstack/react-query'; +import { createContext, use, type PropsWithChildren } from 'react'; +import { useApi } from './ApiProvider'; +import { useResponseErrorHandler } from '../hooks/ResponseHelper'; +import type { Schemas } from '../generated/api-client/api-client'; + +export type HealthStatus = Schemas.HealthInfo; + +export type ApiHealthProviderProps = PropsWithChildren; +export type ApiHealthContextType = { + healthStatus: HealthStatus | undefined; +}; + +const ApiHealthContext = createContext(null); + +export const ApiHealthProvider: React.FC = ({ children }) => { + const navigate = useNavigate(); + const { tanstackApiClient } = useApi(); + const { defaultResponseErrorHandler } = useResponseErrorHandler(); + + const { queryOptions: healthInfoQuery } = tanstackApiClient.get('/api/health/info'); + const { data } = useQuery({ + ...healthInfoQuery, + queryFn: async (...args) => { + try { + const data = await healthInfoQuery.queryFn!(...args); + if (!data.is_initialized) { + navigate('/init'); + } + return data; + } catch (error) { + if (defaultResponseErrorHandler(error)) return {} as never; + throw error; + } + }, + }); + + return ( + + {children} + + ); +}; + +export const useApiHealth = (): ApiHealthContextType => { + const context = use(ApiHealthContext); + if (!context) { + throw new Error('useApiHealth must be used within an ApiHealthProvider'); + } + return context; +}; diff --git a/apps/frontend/app/providers/ApiProvider.tsx b/apps/frontend/app/providers/ApiProvider.tsx index e8e1176..d178119 100644 --- a/apps/frontend/app/providers/ApiProvider.tsx +++ b/apps/frontend/app/providers/ApiProvider.tsx @@ -1,9 +1,9 @@ -import { createContext, use, useContext, type PropsWithChildren } from 'react'; +import { createContext, use, type PropsWithChildren } from 'react'; import { createTanstackApi, createApi } from '../lib/api'; import axios from 'axios'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -type ApiProviderProps = PropsWithChildren<{}>; +type ApiProviderProps = PropsWithChildren; type ApiContextType = { apiClient: ReturnType; tanstackApiClient: ReturnType; @@ -34,8 +34,14 @@ export const ApiProvider: React.FC = ({ children }) => { const axiosInstance = axios.create({ withCredentials: true, }); + + const internalAxiosInstance = axios.create({ + withCredentials: true, + }); + const apiClient = createApi(axiosInstance); - const tanstackApiClient = createTanstackApi(axiosInstance); + const tanstackApiClient = createTanstackApi(internalAxiosInstance); + return ( ; +export type AuthContextType = { + setUser: (user: User) => void; + logOut: () => void; + user: User | null; +}; + +const AuthContext = createContext(null); + +export const AuthProvider: React.FC = ({ children }) => { + const [user, setUserState] = useState(null); + + const setUser = useCallback((user: User) => { + setUserState(user); + }, []); + + const logout = useCallback(() => { + setUserState(null); + }, []); + + return ( + + {children} + + ); +}; + +export function useAuth() { + const context = use(AuthContext); + if (!context) { + throw new Error('useAuth must be used within a AuthProvider'); + } + return context; +} diff --git a/apps/frontend/app/providers/FormProvider.tsx b/apps/frontend/app/providers/FormProvider.tsx new file mode 100644 index 0000000..3ca9931 --- /dev/null +++ b/apps/frontend/app/providers/FormProvider.tsx @@ -0,0 +1,18 @@ +import { createFormHook, createFormHookContexts } from '@tanstack/react-form'; +import { TextField, TextFieldErrorMessage } from '../components/Form/TextField'; +import { ResetButton, SubmitButton } from '../components/Form/Button'; + +const { fieldContext, formContext } = createFormHookContexts(); + +export const formHook = createFormHook({ + fieldComponents: { + TextField, + TextFieldErrorMessage, + }, + formComponents: { + SubmitButton, + ResetButton, + }, + fieldContext, + formContext, +}); diff --git a/apps/frontend/app/providers/LayoutProvider.tsx b/apps/frontend/app/providers/LayoutProvider.tsx new file mode 100644 index 0000000..fd8e564 --- /dev/null +++ b/apps/frontend/app/providers/LayoutProvider.tsx @@ -0,0 +1,38 @@ +import { createContext, use, useState, type PropsWithChildren } from 'react'; +import type { NavItem } from '../components/layout/types'; + +type LayoutProviderProps = PropsWithChildren; +type LayoutContextType = { + activeTab: NavItem; + setActiveTab: (tab: NavItem) => void; + isMobileMenuOpen: boolean; + setIsMobileMenuOpen: (open: boolean) => void; +}; + +const LayoutContext = createContext(null); + +export const LayoutProvider: React.FC = ({ children }) => { + const [activeTab, setActiveTab] = useState('Dashboard'); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + + return ( + + {children} + + ); +}; + +export function useLayout() { + const context = use(LayoutContext); + if (!context) { + throw new Error('useLayout must be used within a LayoutProvider'); + } + return context; +} diff --git a/apps/frontend/app/root.tsx b/apps/frontend/app/root.tsx index 2041ae0..4000cf7 100644 --- a/apps/frontend/app/root.tsx +++ b/apps/frontend/app/root.tsx @@ -1,8 +1,18 @@ import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router'; import type { Route } from './+types/root'; +import '@radix-ui/themes/styles.css'; import './app.css'; -import { Theme } from '@radix-ui/themes'; +// start: react-toastify special import +// ! MUST use unstyled version for dev server build, styled version for production build is handled in vite.config.ts +import { ToastContainer } from 'react-toastify/unstyled'; +import 'react-toastify/ReactToastify.css'; +// end: react-toastify special import +import AppTheme from './components/theme'; import { ApiProvider } from './providers/ApiProvider'; +import { LayoutProvider } from './providers/LayoutProvider'; +import { Tooltip } from 'radix-ui'; +import { AuthProvider } from './providers/AuthProvider'; +import { ApiHealthProvider } from './providers/ApiHealthProvider'; export const links: Route.LinksFunction = () => []; @@ -26,11 +36,23 @@ export function Layout({ children }: { children: React.ReactNode }) { export default function App() { return ( - - - - - + <> + + + + + + + + + + + + + + + + ); } diff --git a/apps/frontend/app/routes.ts b/apps/frontend/app/routes.ts index 9411f45..baff85a 100644 --- a/apps/frontend/app/routes.ts +++ b/apps/frontend/app/routes.ts @@ -1,7 +1,9 @@ -import { type RouteConfig, index, route } from '@react-router/dev/routes'; +import { type RouteConfig, index, layout, route } from '@react-router/dev/routes'; export default [ - index('routes/home.tsx'), + route('login', 'routes/auth/login.tsx'), + route('init', 'routes/init.tsx'), + layout('routes/layout.tsx', [index('routes/home.tsx')]), // catch-all 404 route route('*', 'routes/404.tsx'), ] satisfies RouteConfig; diff --git a/apps/frontend/app/routes/auth/login.tsx b/apps/frontend/app/routes/auth/login.tsx new file mode 100644 index 0000000..9c1486e --- /dev/null +++ b/apps/frontend/app/routes/auth/login.tsx @@ -0,0 +1,153 @@ +import { Box, Container, Flex, Heading } from '@radix-ui/themes'; +import { useMutation } from '@tanstack/react-query'; +import { useLocation, useNavigate } from 'react-router'; +import { toast } from 'react-toastify/unstyled'; +import * as v from 'valibot'; +import { useResponseErrorHandler } from '../../hooks/ResponseHelper'; +import { useApi } from '../../providers/ApiProvider'; +import { formHook } from '../../providers/FormProvider'; +import type { Route } from './+types/login'; +import { SearchParamKeys } from '../../lib/constants'; +import { AxiosError } from 'axios'; +import { useQueryMessage } from '../../hooks/useQueryMessage'; + +const loginFormSchema = v.object({ + username: v.pipe(v.string(), v.trim(), v.minLength(1, 'Username is required')), + password: v.pipe(v.string(), v.minLength(1, 'Password is required')), +}); + +// eslint-disable-next-line no-empty-pattern +export function meta({}: Route.MetaArgs): Route.MetaDescriptors { + return [{ title: 'Login | YANPM' }]; +} + +// TODO: remember me +export default function LoginRoute() { + const navigate = useNavigate(); + const location = useLocation(); + const { tanstackApiClient } = useApi(); + const { defaultResponseErrorHandler } = useResponseErrorHandler(); + useQueryMessage(); + + const { mutateAsync: login, isPending } = useMutation({ + ...tanstackApiClient.mutation('post', '/api/auth/login').mutationOptions, + onSuccess: async () => { + const searchParams = new URLSearchParams(location.search); + const redirectTo = searchParams.get(SearchParamKeys.Redirect); + if (redirectTo) { + navigate(redirectTo); + return; + } + navigate('/'); + }, + onError: (error) => { + if (defaultResponseErrorHandler(error, { disableUnauthorizedHandling: true })) return; + if (error instanceof AxiosError && error.status === 401) { + toast.error('Invalid username or password.', { + position: 'top-center', + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: false, + progress: undefined, + theme: 'colored', + }); + return; + } + console.error('Login failed:', error); + }, + }); + + const form = formHook.useAppForm({ + defaultValues: { + username: '', + password: '', + }, + validators: { + onBlur: loginFormSchema, + onSubmit: loginFormSchema, + }, + + onSubmit: async ({ value }) => { + toast.dismiss(); + return await login({ body: { password: value.password, username: value.username } }).catch(() => {}); + }, + }); + + return ( + <> + + + + + Sign In + +
{ + e.preventDefault(); + form.handleSubmit(); + }} + > + ( + <> + field.handleChange(e.target.value)} + /> + + + )} + /> + + ( + <> + field.handleChange(e.target.value)} + showPasswordToggle + /> + + + )} + /> + +
+ +
+ +
+
+
+ + ); +} diff --git a/apps/frontend/app/routes/home.tsx b/apps/frontend/app/routes/home.tsx index 8a5c77b..7a11e97 100644 --- a/apps/frontend/app/routes/home.tsx +++ b/apps/frontend/app/routes/home.tsx @@ -1,13 +1,75 @@ -import { Text } from '@radix-ui/themes'; +import { Box, Button, Card, Flex, Grid, Heading, Text } from '@radix-ui/themes'; import type { Route } from './+types/home'; -import { useContext } from 'react'; -import { useApi } from '../providers/ApiProvider'; -import { useQuery } from '@tanstack/react-query'; +import TablePlaceholder from '../components/home/TablePlaceholder'; +import { useLayout } from '../providers/LayoutProvider'; +import { useEnsureLoggedIn } from '../hooks/ensureLoggedIn'; +// eslint-disable-next-line no-empty-pattern export function meta({}: Route.MetaArgs) { - return [{ title: 'YANPM' }, { name: 'description', content: 'Welcome to Yet Another Nginx Proxy Manager!' }]; + return [{ title: 'Proxy Host Demo | YANPM' }, { name: 'description', content: 'Demo of the unified navigation paradigm.' }]; } -export default function Home() { - return Welcome to Yet Another Nginx Proxy Manager!; +export default function ProxyHostDemo() { + useEnsureLoggedIn(); + const { activeTab } = useLayout(); + return ( + + + {activeTab} + + + This is the {activeTab.toLowerCase()} page demo. + + + + + + + Status Overview + + + Everything is running smoothly in your {activeTab.toLowerCase()} section. + + + + + + + + Recent Activity + + + No recent changes detected in the last 24 hours. + + + + + + + + Quick Actions + + + Common tasks related to {activeTab.toLowerCase()} are available here. + + + + + + + {activeTab === 'Proxy Hosts' && ( + + + + + + )} + + ); } diff --git a/apps/frontend/app/routes/init.tsx b/apps/frontend/app/routes/init.tsx new file mode 100644 index 0000000..5909a83 --- /dev/null +++ b/apps/frontend/app/routes/init.tsx @@ -0,0 +1,161 @@ +import { Box, Container, Flex, Heading, Text } from '@radix-ui/themes'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { useNavigate } from 'react-router'; +import { toast } from 'react-toastify/unstyled'; +import * as v from 'valibot'; +import { useResponseErrorHandler } from '../hooks/ResponseHelper'; +import { useApi } from '../providers/ApiProvider'; +import { formHook } from '../providers/FormProvider'; +import { TooltipContentContainer } from '../components/info'; +import { SearchParamKeys } from '../lib/constants'; +import { useQueryMessage } from '../hooks/useQueryMessage'; +import { QueryMessageCode, QueryMessageType } from '../lib/QueryMessages'; + +const initFormSchema = v.object({ + username: v.pipe(v.string(), v.trim(), v.minLength(1, 'Username is required')), + password: v.pipe(v.string(), v.minLength(1, 'Password is required')), + setup_secret: v.pipe(v.string(), v.minLength(1, 'Setup secret is required')), +}); + +export default function InitRoute() { + const navigate = useNavigate(); + const { tanstackApiClient } = useApi(); + const { defaultResponseErrorHandler } = useResponseErrorHandler(); + const { toSearchParamQueryMessage } = useQueryMessage(); + + const { mutateAsync: initAdmin, isPending } = useMutation({ + ...tanstackApiClient.mutation('post', '/api/auth/init_admin').mutationOptions, + onSuccess: async () => { + const searchParams = new URLSearchParams(); + searchParams.set(SearchParamKeys.Message, toSearchParamQueryMessage(QueryMessageCode.InitializationSuccessful, QueryMessageType.Success)); + navigate(`/login?${searchParams.toString()}`); + }, + onError: (error) => { + if (defaultResponseErrorHandler(error)) return; + console.error('Init failed:', error); + }, + }); + + const { queryOptions: healthInfoQuery } = tanstackApiClient.get('/api/health/info'); + useQuery({ + ...healthInfoQuery, + queryFn: async (...args) => { + try { + const data = await healthInfoQuery.queryFn!(...args); + if (data.is_initialized) { + navigate('/login', { replace: true }); + return data; + } + return data; + } catch (error) { + if (defaultResponseErrorHandler(error)) return {} as never; + throw error; + } + }, + }); + + const form = formHook.useAppForm({ + defaultValues: { username: '', password: '', setup_secret: '' }, + validators: { onBlur: initFormSchema, onSubmit: initFormSchema }, + onSubmit: async ({ value }) => { + toast.dismiss(); + return await initAdmin({ body: { username: value.username, password: value.password, setup_secret: value.setup_secret } }); + }, + }); + + return ( + <> + + + + + Initialize YANPM + + + + Create the initial admin user + +
{ + e.preventDefault(); + form.handleSubmit(); + }} + > + ( + <> + field.handleChange(e.target.value)} + /> + + + )} + /> + + ( + <> + field.handleChange(e.target.value)} + showPasswordToggle + /> + + + )} + /> + + ( + <> + field.handleChange(e.target.value)} + infoIconProps={{ + children: ( + + This secret is provided when the API server is first started. Refer to your server logs to find it. + + ), + }} + /> + + + )} + /> + +
+ +
+ +
+
+
+ + ); +} diff --git a/apps/frontend/app/routes/layout.tsx b/apps/frontend/app/routes/layout.tsx new file mode 100644 index 0000000..0a9d90e --- /dev/null +++ b/apps/frontend/app/routes/layout.tsx @@ -0,0 +1,88 @@ +import { Flex, Box, Container, Dialog, Heading, IconButton, TextField } from '@radix-ui/themes'; +import SidebarContent from '../components/layout/SidebarContent'; +import { useLayout } from '../providers/LayoutProvider'; +import { Menu, Search, Bell } from 'lucide-react'; +import { Outlet } from 'react-router'; + +export default function LayoutContainer() { + const { activeTab, isMobileMenuOpen, setIsMobileMenuOpen } = useLayout(); + return ( + + {/* Desktop Sidebar */} + + + + + {/* Main Content Area */} + + {' '} + {/* Top Header (Mobile & Desktop) */} + + + + + + + + + + + + + + + {activeTab} + + + + + + + + + + + + + + + + + + + ); +} diff --git a/apps/frontend/app/vite-env.d.ts b/apps/frontend/app/vite-env.d.ts index 32e46ee..3f3a649 100644 --- a/apps/frontend/app/vite-env.d.ts +++ b/apps/frontend/app/vite-env.d.ts @@ -1,7 +1,6 @@ interface ViteTypeOptions { - // By adding this line, you can make the type of ImportMetaEnv strict - // to disallow unknown keys. - // strictImportMetaEnv: unknown + // disallow unknown keys. + strictImportMetaEnv: unknown; } interface ImportMetaEnv { diff --git a/apps/frontend/eslint.config.ts b/apps/frontend/eslint.config.ts new file mode 100644 index 0000000..647d35e --- /dev/null +++ b/apps/frontend/eslint.config.ts @@ -0,0 +1,42 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import pluginReact from 'eslint-plugin-react'; +import pluginReactHooks from 'eslint-plugin-react-hooks'; + +export default tseslint.config( + { + // Ignore files and directories + ignores: ['node_modules', 'app/generated', 'build', '.react-router'], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + ecmaVersion: 2020, + globals: { + ...globals.browser, + ...globals.node, + }, + parserOptions: { + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: {}, + }, + { + ...pluginReact.configs.flat.recommended, // Enables core React rules + ...pluginReactHooks.configs.flat.recommended, // Enables React Hooks rules + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.browser, + }, + }, + } +); diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 9f9b8a4..27cecbc 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -7,32 +7,45 @@ "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", "typecheck": "react-router typegen && tsc", + "lint": "eslint .", "test": "echo \"No tests specified\" && exit 0", "generate:openapi": "typed-openapi ../api/swagger.json --tanstack tanstack-client.ts -o ./app/generated/api-client/api-client.ts" }, "dependencies": { + "@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/themes": "^3.2.1", "@react-router/node": "^7.9.2", "@react-router/serve": "^7.9.2", + "@tanstack/react-form": "^1.27.5", "@tanstack/react-query": "^5.90.12", "axios": "^1.13.2", + "globals": "^16.5.0", "isbot": "^5.1.31", + "lucide-react": "^0.562.0", "radix-ui": "^1.4.3", "react": "^19.1.1", "react-dom": "^19.1.1", - "react-router": "^7.9.2" + "react-router": "^7.9.2", + "react-toastify": "^11.0.5", + "valibot": "^1.2.0" }, "devDependencies": { + "@eslint/js": "^9.39.2", "@react-router/dev": "^7.9.2", "@tailwindcss/vite": "^4.1.13", "@types/node": "^22", "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", "dotenv": "^17.2.3", + "eslint": "^9.39.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", "tailwindcss": "^4.1.13", "typed-openapi": "^2.2.3", "typescript": "^5.9.2", + "typescript-eslint": "^8.50.0", "vite": "^7.1.7", + "vite-plugin-eslint": "^1.8.1", "vite-tsconfig-paths": "^5.1.4" } } \ No newline at end of file diff --git a/apps/frontend/pnpm-lock.yaml b/apps/frontend/pnpm-lock.yaml index 21e2b01..81359e2 100644 --- a/apps/frontend/pnpm-lock.yaml +++ b/apps/frontend/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/themes': specifier: ^3.2.1 version: 3.2.1(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -17,15 +20,24 @@ importers: '@react-router/serve': specifier: ^7.9.2 version: 7.9.6(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(typescript@5.9.3) + '@tanstack/react-form': + specifier: ^1.27.5 + version: 1.27.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tanstack/react-query': specifier: ^5.90.12 version: 5.90.12(react@19.2.0) axios: specifier: ^1.13.2 version: 1.13.2 + globals: + specifier: ^16.5.0 + version: 16.5.0 isbot: specifier: ^5.1.31 version: 5.1.32 + lucide-react: + specifier: ^0.562.0 + version: 0.562.0(react@19.2.0) radix-ui: specifier: ^1.4.3 version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -38,7 +50,16 @@ importers: react-router: specifier: ^7.9.2 version: 7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react-toastify: + specifier: ^11.0.5 + version: 11.0.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + valibot: + specifier: ^1.2.0 + version: 1.2.0(typescript@5.9.3) devDependencies: + '@eslint/js': + specifier: ^9.39.2 + version: 9.39.2 '@react-router/dev': specifier: ^7.9.2 version: 7.9.6(@react-router/serve@7.9.6(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(typescript@5.9.3))(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))(yaml@2.8.2) @@ -57,6 +78,15 @@ importers: dotenv: specifier: ^17.2.3 version: 17.2.3 + eslint: + specifier: ^9.39.2 + version: 9.39.2(jiti@2.6.1) + eslint-plugin-react: + specifier: ^7.37.5 + version: 7.37.5(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.2(jiti@2.6.1)) tailwindcss: specifier: ^4.1.13 version: 4.1.17 @@ -66,9 +96,15 @@ importers: typescript: specifier: ^5.9.2 version: 5.9.3 + typescript-eslint: + specifier: ^8.50.0 + version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.1.7 version: 7.2.6(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + vite-plugin-eslint: + specifier: ^1.8.1 + version: 1.8.1(eslint@9.39.2(jiti@2.6.1))(vite@7.2.6(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) vite-tsconfig-paths: specifier: ^5.1.4 version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) @@ -382,6 +418,44 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} @@ -397,6 +471,22 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1197,6 +1287,10 @@ packages: '@remix-run/node-fetch-server@0.9.0': resolution: {integrity: sha512-SoLMv7dbH+njWzXnOY6fI08dFMI5+/dQ+vY3n8RnnbdG7MdJEgiP28Xj/xWlnRnED/aB6SFw56Zop+LbmaaKqA==} + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + '@rollup/rollup-android-arm-eabi@4.53.3': resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] @@ -1403,14 +1497,49 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@tanstack/devtools-event-client@0.4.0': + resolution: {integrity: sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw==} + engines: {node: '>=18'} + + '@tanstack/form-core@1.27.5': + resolution: {integrity: sha512-A8EriWfn+2QsxEbzt6AXn6CC6ILv3VPDXhBDAeE5LTiCHryy7xuj+zo1Q2JWBkhJxS1AuEgY1BZr5AP2mWiHQQ==} + + '@tanstack/pacer-lite@0.1.1': + resolution: {integrity: sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==} + engines: {node: '>=18'} + '@tanstack/query-core@5.90.12': resolution: {integrity: sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==} + '@tanstack/react-form@1.27.5': + resolution: {integrity: sha512-B0nSrlOh4+i/zq2U2ezyRYVz4+6vVzviVAFSZL3FCrJiwryEPM4/0E/RpwkbMZiY2UHp/4KHenPcBQZGhXS+tw==} + peerDependencies: + '@tanstack/react-start': '*' + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@tanstack/react-start': + optional: true + '@tanstack/react-query@5.90.12': resolution: {integrity: sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==} peerDependencies: react: ^18 || ^19 + '@tanstack/react-store@0.8.0': + resolution: {integrity: sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/store@0.7.7': + resolution: {integrity: sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==} + + '@tanstack/store@0.8.0': + resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} + + '@types/eslint@8.56.12': + resolution: {integrity: sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1428,10 +1557,79 @@ packages: '@types/react@19.2.7': resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + '@typescript-eslint/eslint-plugin@8.50.0': + resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.50.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.50.0': + resolution: {integrity: sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.50.0': + resolution: {integrity: sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.50.0': + resolution: {integrity: sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.50.0': + resolution: {integrity: sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.50.0': + resolution: {integrity: sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.50.0': + resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.50.0': + resolution: {integrity: sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.50.0': + resolution: {integrity: sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.50.0': + resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -1440,6 +1638,9 @@ packages: ajv: optional: true + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} @@ -1472,12 +1673,48 @@ packages: arktype@2.1.20: resolution: {integrity: sha512-IZCEEXaJ8g+Ijd59WtSYwtjnqXiwM8sWQ5EjGamcto7+HVN9eK0C4p0zDlCuAwWhpqr6fIBkxPuYDl4/Mcj/+Q==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + axios@1.13.2: resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} @@ -1499,6 +1736,9 @@ packages: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -1522,6 +1762,10 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -1529,9 +1773,17 @@ packages: call-me-maybe@1.0.2: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + caniuse-lite@1.0.30001757: resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -1539,6 +1791,10 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1558,6 +1814,9 @@ packages: resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} engines: {node: '>= 0.8.0'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1587,6 +1846,18 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -1612,6 +1883,17 @@ packages: babel-plugin-macros: optional: true + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1631,6 +1913,10 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dotenv@17.2.3: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} @@ -1669,6 +1955,10 @@ packages: err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1677,6 +1967,10 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-iterator-helpers@1.2.2: + resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -1688,6 +1982,14 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -1700,6 +2002,67 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -1715,6 +2078,12 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1727,10 +2096,25 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + finalhandler@1.3.2: resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -1740,6 +2124,10 @@ packages: debug: optional: true + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -1764,6 +2152,17 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1784,10 +2183,30 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -1798,6 +2217,21 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1810,6 +2244,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hosted-git-info@6.1.3: resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1826,21 +2266,136 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isbot@5.1.32: resolution: {integrity: sha512-VNfjM73zz2IBZmdShMfAUg10prm6t7HFUQmNAEOAVS4YH92ZrZcvkMcGX6cIgBJAzWDzPent/EeAtYEHNPNPBQ==} engines: {node: '>=18'} @@ -1848,6 +2403,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -1867,18 +2426,38 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@3.0.2: resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -1949,9 +2528,20 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -1962,6 +2552,11 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + lucide-react@0.562.0: + resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1997,6 +2592,9 @@ packages: engines: {node: '>=4'} hasBin: true + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -2020,6 +2618,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -2051,10 +2652,34 @@ packages: resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -2073,6 +2698,22 @@ packages: openapi3-ts@4.5.0: resolution: {integrity: sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-map@7.0.4: resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} engines: {node: '>=18'} @@ -2080,6 +2721,10 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -2096,10 +2741,17 @@ packages: xstate: optional: true + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} @@ -2116,14 +2768,26 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} @@ -2155,6 +2819,9 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -2162,6 +2829,10 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -2192,6 +2863,9 @@ packages: peerDependencies: react: ^19.2.0 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -2236,6 +2910,12 @@ packages: '@types/react': optional: true + react-toastify@11.0.5: + resolution: {integrity: sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==} + peerDependencies: + react: ^18 || ^19 + react-dom: ^18 || ^19 + react@19.2.0: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} @@ -2244,25 +2924,58 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + rollup@2.79.2: + resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==} + engines: {node: '>=10.0.0'} + hasBin: true + rollup@4.53.3: resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -2293,6 +3006,18 @@ packages: set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -2355,6 +3080,10 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2363,6 +3092,25 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2371,6 +3119,18 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + tailwindcss@4.1.17: resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} @@ -2386,6 +3146,12 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-pattern@5.9.0: resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==} @@ -2405,6 +3171,10 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-fest@3.13.1: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} @@ -2413,15 +3183,42 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + typed-openapi@2.2.3: resolution: {integrity: sha512-ZcecDxLjHuirwYmDeObAHJjuOxIut9M8FMSeKcVIGPdurYZ+c8dQwxdXTIJ+w7umnv61da1GGSuthROI684rmA==} hasBin: true + typescript-eslint@8.50.0: + resolution: {integrity: sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -2435,6 +3232,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -2488,6 +3288,12 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite-plugin-eslint@1.8.1: + resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==} + peerDependencies: + eslint: '>=7' + vite: '>=2' + vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -2536,6 +3342,22 @@ packages: yaml: optional: true + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2546,6 +3368,10 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -2562,6 +3388,19 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.2.1: + resolution: {integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==} + snapshots: '@apidevtools/json-schema-ref-parser@11.7.2': @@ -2855,6 +3694,52 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))': + dependencies: + eslint: 9.39.2(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 @@ -2872,6 +3757,17 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3780,6 +4676,11 @@ snapshots: '@remix-run/node-fetch-server@0.9.0': {} + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + '@rollup/rollup-android-arm-eabi@4.53.3': optional: true @@ -3922,13 +4823,47 @@ snapshots: tailwindcss: 4.1.17 vite: 7.2.6(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + '@tanstack/devtools-event-client@0.4.0': {} + + '@tanstack/form-core@1.27.5': + dependencies: + '@tanstack/devtools-event-client': 0.4.0 + '@tanstack/pacer-lite': 0.1.1 + '@tanstack/store': 0.7.7 + + '@tanstack/pacer-lite@0.1.1': {} + '@tanstack/query-core@5.90.12': {} + '@tanstack/react-form@1.27.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@tanstack/form-core': 1.27.5 + '@tanstack/react-store': 0.8.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + transitivePeerDependencies: + - react-dom + '@tanstack/react-query@5.90.12(react@19.2.0)': dependencies: '@tanstack/query-core': 5.90.12 react: 19.2.0 + '@tanstack/react-store@0.8.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@tanstack/store': 0.8.0 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@19.2.0) + + '@tanstack/store@0.7.7': {} + + '@tanstack/store@0.8.0': {} + + '@types/eslint@8.56.12': + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -3945,15 +4880,119 @@ snapshots: dependencies: csstype: 3.2.3 + '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/type-utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.0 + eslint: 9.39.2(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.0 + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.50.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.50.0': + dependencies: + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/visitor-keys': 8.50.0 + + '@typescript-eslint/tsconfig-utils@8.50.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.50.0': {} + + '@typescript-eslint/typescript-estree@8.50.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.50.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/visitor-keys': 8.50.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.50.0': + dependencies: + '@typescript-eslint/types': 8.50.0 + eslint-visitor-keys: 4.2.1 + accepts@1.3.8: dependencies: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + ajv-draft-04@1.0.0(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 @@ -3984,10 +5023,73 @@ snapshots: '@ark/schema': 0.46.0 '@ark/util': 0.46.0 + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + array-flatten@1.1.1: {} + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + async-function@1.0.0: {} + asynckit@0.4.0: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + axios@1.13.2: dependencies: follow-redirects: 1.15.11 @@ -4030,6 +5132,11 @@ snapshots: transitivePeerDependencies: - supports-color + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -4053,6 +5160,13 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4060,14 +5174,23 @@ snapshots: call-me-maybe@1.0.2: {} + callsites@3.1.0: {} + caniuse-lite@1.0.30001757: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 classnames@2.5.1: {} + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -4094,6 +5217,8 @@ snapshots: transitivePeerDependencies: - supports-color + concat-map@0.0.1: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -4116,6 +5241,24 @@ snapshots: csstype@3.2.3: {} + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + debug@2.6.9: dependencies: ms: 2.0.0 @@ -4126,6 +5269,20 @@ snapshots: dedent@1.7.0: {} + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + delayed-stream@1.0.0: {} depd@2.0.0: {} @@ -4136,6 +5293,10 @@ snapshots: detect-node-es@1.1.0: {} + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + dotenv@17.2.3: {} dunder-proto@1.0.1: @@ -4165,10 +5326,86 @@ snapshots: err-code@2.0.3: {} + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + es-define-property@1.0.1: {} es-errors@1.3.0: {} + es-iterator-helpers@1.2.2: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + es-module-lexer@1.7.0: {} es-object-atoms@1.1.1: @@ -4182,6 +5419,16 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -4215,6 +5462,111 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + eslint: 9.39.2(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.2.1 + zod-validation-error: 4.0.2(zod@4.2.1) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.2 + eslint: 9.39.2(jiti@2.6.1) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + etag@1.8.1: {} exit-hook@2.2.1: {} @@ -4257,12 +5609,20 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fast-uri@3.1.0: {} fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + finalhandler@1.3.2: dependencies: debug: 2.6.9 @@ -4275,8 +5635,24 @@ snapshots: transitivePeerDependencies: - supports-color + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + follow-redirects@1.15.11: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -4299,6 +5675,19 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} get-intrinsic@1.3.0: @@ -4323,6 +5712,16 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@10.5.0: dependencies: foreground-child: 3.3.1 @@ -4332,12 +5731,33 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + globals@14.0.0: {} + + globals@16.5.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + globrex@0.1.2: {} gopd@1.2.0: {} graceful-fs@4.2.11: {} + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -4348,6 +5768,12 @@ snapshots: dependencies: function-bind: 1.1.2 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + hosted-git-info@6.1.3: dependencies: lru-cache: 7.18.3 @@ -4372,20 +5798,150 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + inherits@2.0.4: {} + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + ipaddr.js@1.9.1: {} + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + isbot@5.1.32: {} isexe@2.0.0: {} + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -4402,12 +5958,34 @@ snapshots: jsesc@3.0.2: {} + json-buffer@3.0.1: {} + json-parse-even-better-errors@3.0.2: {} + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lightningcss-android-arm64@1.30.2: optional: true @@ -4457,8 +6035,18 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + lodash@4.17.21: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -4467,6 +6055,10 @@ snapshots: lru-cache@7.18.3: {} + lucide-react@0.562.0(react@19.2.0): + dependencies: + react: 19.2.0 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4489,6 +6081,10 @@ snapshots: mime@1.6.0: {} + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -4511,6 +6107,8 @@ snapshots: nanoid@3.3.11: {} + natural-compare@1.4.0: {} + negotiator@0.6.3: {} negotiator@0.6.4: {} @@ -4544,8 +6142,42 @@ snapshots: npm-package-arg: 10.1.0 semver: 7.7.3 + object-assign@4.1.1: {} + object-inspect@1.13.4: {} + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -4562,10 +6194,37 @@ snapshots: dependencies: yaml: 2.8.2 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-map@7.0.4: {} package-json-from-dist@1.0.1: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parseurl@1.3.3: {} pastable@2.2.1(react@19.2.0): @@ -4578,8 +6237,12 @@ snapshots: transitivePeerDependencies: - supports-color + path-exists@4.0.0: {} + path-key@3.1.1: {} + path-parse@1.0.7: {} + path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 @@ -4593,14 +6256,20 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.3: {} + possible-typed-array-names@1.1.0: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + prettier@2.8.8: {} prettier@3.5.3: {} @@ -4616,6 +6285,12 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -4623,6 +6298,8 @@ snapshots: proxy-from-env@1.1.0: {} + punycode@2.3.1: {} + qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -4704,6 +6381,8 @@ snapshots: react: 19.2.0 scheduler: 0.27.0 + react-is@16.13.1: {} + react-refresh@0.14.2: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.0): @@ -4741,14 +6420,52 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + react-toastify@11.0.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react@19.2.0: {} readdirp@4.1.2: {} + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + retry@0.12.0: {} + rollup@2.79.2: + optionalDependencies: + fsevents: 2.3.3 + rollup@4.53.3: dependencies: '@types/estree': 1.0.8 @@ -4777,10 +6494,29 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + safer-buffer@2.1.2: {} scheduler@0.27.0: {} @@ -4836,6 +6572,28 @@ snapshots: set-cookie-parser@2.7.2: {} + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -4901,6 +6659,11 @@ snapshots: statuses@2.0.2: {} + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4913,6 +6676,50 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -4921,6 +6728,14 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + tailwindcss@4.1.17: {} tapable@2.3.0: {} @@ -4932,6 +6747,10 @@ snapshots: toidentifier@1.0.1: {} + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-pattern@5.9.0: {} ts-toolbelt@9.6.0: {} @@ -4942,6 +6761,10 @@ snapshots: tslib@2.8.1: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-fest@3.13.1: {} type-is@1.6.18: @@ -4949,6 +6772,39 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + typed-openapi@2.2.3(openapi-types@12.1.3)(react@19.2.0): dependencies: '@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3) @@ -4966,8 +6822,26 @@ snapshots: - supports-color - xstate + typescript-eslint@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + undici-types@6.21.0: {} unpipe@1.0.0: {} @@ -4978,6 +6852,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.0): dependencies: react: 19.2.0 @@ -5033,6 +6911,14 @@ snapshots: - tsx - yaml + vite-plugin-eslint@1.8.1(eslint@9.39.2(jiti@2.6.1))(vite@7.2.6(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)): + dependencies: + '@rollup/pluginutils': 4.2.1 + '@types/eslint': 8.56.12 + eslint: 9.39.2(jiti@2.6.1) + rollup: 2.79.2 + vite: 7.2.6(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)): dependencies: debug: 4.4.3 @@ -5059,6 +6945,47 @@ snapshots: lightningcss: 1.30.2 yaml: 2.8.2 + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -5067,6 +6994,8 @@ snapshots: dependencies: isexe: 2.0.0 + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -5082,3 +7011,11 @@ snapshots: yallist@3.1.1: {} yaml@2.8.2: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.2.1): + dependencies: + zod: 4.2.1 + + zod@4.2.1: {} diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts index 22b2eb2..09a6a85 100644 --- a/apps/frontend/vite.config.ts +++ b/apps/frontend/vite.config.ts @@ -2,8 +2,37 @@ import { reactRouter } from '@react-router/dev/vite'; import tailwindcss from '@tailwindcss/vite'; import { defineConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; +// @ts-expect-error vite-plugin-eslint has no types +import eslint from 'vite-plugin-eslint'; -export default defineConfig({ - plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], - appType: 'spa', +export default defineConfig(({ command }) => { + const isBuild = command === 'build'; + + return { + plugins: [ + tailwindcss(), + reactRouter(), + tsconfigPaths(), + eslint({ + failOnError: false, + }), + ], + resolve: { + alias: isBuild + ? [ + { + // replace unstyled import with styled for SPA build + find: 'react-toastify/unstyled', + replacement: 'react-toastify', + }, + { + // point to the empty CSS file to stub out the import during build, SPA build does not require extra CSS imports + find: 'react-toastify/ReactToastify.css', + replacement: '~/empty-toastify.css', + }, + ] + : [], + }, + appType: 'spa', + }; });